private ConflictCallback ToOnGameThread(ConflictCallback conflictCallback) { return((resolver, original, originalData, unmerged, unmergedData) => { Logger.d("Invoking conflict callback"); PlayGamesHelperObject.RunOnGameThread(() => conflictCallback(resolver, original, originalData, unmerged, unmergedData)); }); }
public void OpenWithAutomaticConflictResolution(string filename, DataSource source, ConflictResolutionStrategy resolutionStrategy, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { Misc.CheckNotNull(filename); Misc.CheckNotNull(completedCallback); var prefetchDataOnConflict = false; ConflictCallback conflictCallback = null; completedCallback = ToOnGameThread(completedCallback); if (conflictCallback == null) { conflictCallback = (resolver, original, originalData, unmerged, unmergedData) => { switch (resolutionStrategy) { case ConflictResolutionStrategy.UseOriginal: resolver.ChooseMetadata(original); return; case ConflictResolutionStrategy.UseUnmerged: resolver.ChooseMetadata(unmerged); return; case ConflictResolutionStrategy.UseLongestPlaytime: if (original.TotalTimePlayed >= unmerged.TotalTimePlayed) { resolver.ChooseMetadata(original); } else { resolver.ChooseMetadata(unmerged); } return; default: Logger.e("Unhandled strategy " + resolutionStrategy); completedCallback(SavedGameRequestStatus.InternalError, null); return; } } } ; conflictCallback = ToOnGameThread(conflictCallback); if (!IsValidFilename(filename)) { Logger.e("Received invalid filename: " + filename); completedCallback(SavedGameRequestStatus.BadInputError, null); return; } InternalOpen(filename, source, resolutionStrategy, prefetchDataOnConflict, conflictCallback, completedCallback); }
private ConflictCallback ToOnGameThread(ConflictCallback conflictCallback) { return(delegate(IConflictResolver resolver, ISavedGameMetadata original, byte[] originalData, ISavedGameMetadata unmerged, byte[] unmergedData) { Logger.d("Invoking conflict callback"); PlayGamesHelperObject.RunOnGameThread(delegate { conflictCallback(resolver, original, originalData, unmerged, unmergedData); }); }); }
public void OpenWithManualConflictResolution(string filename, DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { Misc.CheckNotNull(filename); Misc.CheckNotNull(conflictCallback); Misc.CheckNotNull(completedCallback); conflictCallback = ToOnGameThread(conflictCallback); completedCallback = ToOnGameThread(completedCallback); if (!IsValidFilename(filename)) { OurUtils.Logger.e("Received invalid filename: " + filename); completedCallback(SavedGameRequestStatus.BadInputError, null); return; } InternalOpen(filename, source, ConflictResolutionStrategy.UseManual, prefetchDataOnConflict, conflictCallback, completedCallback); }
void Open(ISavedGameClient savedGameClient, bool useAutomaticResolution, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { if (useAutomaticResolution) { savedGameClient.OpenWithAutomaticConflictResolution( PlatformSaveUtil.remoteSaveFileName, DataSource.ReadNetworkOnly, ConflictResolutionStrategy.UseLongestPlaytime, completedCallback); } else { savedGameClient.OpenWithManualConflictResolution( PlatformSaveUtil.remoteSaveFileName, DataSource.ReadNetworkOnly, true, conflictCallback, completedCallback); } }
/// <summary> /// Applies a given change set to provided db /// with possibility to limit which tables are changed via xFilter delegate, /// and custom conflict resolution via xConflict delegate /// See https://sqlite.org/session/sqlite3changeset_apply.html /// </summary> /// <param name="db">which db ("main" only) to apply changes to</param> /// <param name="changeSet">change set to apply to db</param> /// <param name="xFilter">use null to not filter, else delegate to limit tables changes applied to</param> /// <param name="xConflict">use null to ignore conflicts, else delegate to handle conflicts</param> /// <param name="ctx">context passed as first argument to xFilter and xConflict delegates</param> /// <returns></returns> public static Result ChangeSetApply(SQLite.SQLiteConnection db, SQLiteChangeSet changeSet, FilterCallback xFilter, ConflictCallback xConflict, object ctx) { Sqlite3DatabaseHandle dbHandle = db?.Handle ?? IntPtr.Zero; if (dbHandle == IntPtr.Zero) { return(Result.Misuse); } if (xConflict == null) { xConflict = new ConflictCallback(CallbackIgnoreConflicts); } // pinning not needed since just passing back thru; see https://blogs.msdn.microsoft.com/jmstall/2006/10/09/gchandle-tointptr-vs-gchandle-addrofpinnedobject/ // Warning: if conflict handler is in unmanaged code then ctx should be pinned in Alloc and use AddrOfPinnedObject instead of ToIntPtr GCHandle gch = GCHandle.Alloc(ctx); // ok if ctx is null; pinning is unneeded since not kept past ChangeSetApply call IntPtr pCtx = (ctx == null) ? IntPtr.Zero : GCHandle.ToIntPtr(gch); // we don't pass GCHandle wrapper if null, instead pass NULL var result = ChangeSetApply(dbHandle, changeSet.size, changeSet.buffer, xFilter, xConflict, pCtx); gch.Free(); return(result); }
private void InternalOpen(string filename, DataSource source, ConflictResolutionStrategy resolutionStrategy, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { int conflictPolicy; // SnapshotsClient.java#RetentionPolicy switch (resolutionStrategy) { case ConflictResolutionStrategy.UseLastKnownGood: conflictPolicy = 2 /* RESOLUTION_POLICY_LAST_KNOWN_GOOD */; break; case ConflictResolutionStrategy.UseMostRecentlySaved: conflictPolicy = 3 /* RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED */; break; case ConflictResolutionStrategy.UseLongestPlaytime: conflictPolicy = 1 /* RESOLUTION_POLICY_LONGEST_PLAYTIME*/; break; case ConflictResolutionStrategy.UseManual: conflictPolicy = -1 /* RESOLUTION_POLICY_MANUAL */; break; default: conflictPolicy = 3 /* RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED */; break; } using (var task = mSnapshotsClient.Call <AndroidJavaObject>("open", filename, /* createIfNotFound= */ true, conflictPolicy)) { AndroidTaskUtils.AddOnSuccessListener <AndroidJavaObject>( task, dataOrConflict => { if (dataOrConflict.Call <bool>("isConflict")) { var conflict = dataOrConflict.Call <AndroidJavaObject>("getConflict"); AndroidSnapshotMetadata original = new AndroidSnapshotMetadata(conflict.Call <AndroidJavaObject>("getSnapshot")); AndroidSnapshotMetadata unmerged = new AndroidSnapshotMetadata( conflict.Call <AndroidJavaObject>("getConflictingSnapshot")); // Instantiate the conflict resolver. Note that the retry callback closes over // all the parameters we need to retry the open attempt. Once the conflict is // resolved by invoking the appropriate resolution method on // AndroidConflictResolver, the resolver will invoke this callback, which will // result in this method being re-executed. This recursion will continue until // all conflicts are resolved or an error occurs. AndroidConflictResolver resolver = new AndroidConflictResolver( this, mSnapshotsClient, conflict, original, unmerged, completedCallback, () => InternalOpen(filename, source, resolutionStrategy, prefetchDataOnConflict, conflictCallback, completedCallback)); var originalBytes = original.JavaContents.Call <byte[]>("readFully"); var unmergedBytes = unmerged.JavaContents.Call <byte[]>("readFully"); conflictCallback(resolver, original, originalBytes, unmerged, unmergedBytes); } else { using (var snapshot = dataOrConflict.Call <AndroidJavaObject>("getData")) { AndroidJavaObject metadata = snapshot.Call <AndroidJavaObject>("freeze"); completedCallback(SavedGameRequestStatus.Success, new AndroidSnapshotMetadata(metadata)); } } }); AddOnFailureListenerWithSignOut( task, exception => { OurUtils.Logger.d("InternalOpen has failed: " + exception.Call <string>("toString")); var status = mAndroidClient.IsAuthenticated() ? SavedGameRequestStatus.InternalError : SavedGameRequestStatus.AuthenticationError; completedCallback(status, null); } ); } }
public void ResolveConflicts(IEnumerable <TConflictItem> conflictItems, Func <TConflictItem, string> getItemKey, ConflictCallback <TConflictItem> foundConflict, bool commitWinner = true) { if (conflictItems == null) { return; } foreach (var conflictItem in conflictItems) { var itemKey = getItemKey(conflictItem); if (String.IsNullOrEmpty(itemKey)) { continue; } TConflictItem existingItem; if (_winningItemsByKey.TryGetValue(itemKey, out existingItem)) { // a conflict was found, determine the winner. var winner = ResolveConflict(existingItem, conflictItem, logUnresolvedConflicts: false); if (winner == null) { // No winner. Keep track of the conflictItem, so that if a subsequent // item wins for this key, both items (for which there was no winner when // compared to each other) can be counted as conflicts and removed from // the corresponding list. List <TConflictItem> unresolvedConflictsForKey; if (!_unresolvedConflictItems.TryGetValue(itemKey, out unresolvedConflictsForKey)) { unresolvedConflictsForKey = new List <TConflictItem>(); _unresolvedConflictItems[itemKey] = unresolvedConflictsForKey; // This is the first time we hit an unresolved conflict for this key, so // add the existing item to the unresolved conflicts list unresolvedConflictsForKey.Add(existingItem); } // Add the new item to the unresolved conflicts list unresolvedConflictsForKey.Add(conflictItem); continue; } TConflictItem loser = conflictItem; if (!ReferenceEquals(winner, existingItem)) { // replace existing item if (commitWinner) { _winningItemsByKey[itemKey] = conflictItem; } else { _winningItemsByKey.Remove(itemKey); } loser = existingItem; } foundConflict(winner, loser); // If there were any other items that tied with the loser, report them as conflicts here List <TConflictItem> previouslyUnresolvedConflicts; if (_unresolvedConflictItems.TryGetValue(itemKey, out previouslyUnresolvedConflicts)) { foreach (var previouslyUnresolvedItem in previouslyUnresolvedConflicts) { // Don't re-report the item that just lost and was already reported if (object.ReferenceEquals(previouslyUnresolvedItem, loser)) { continue; } // Call ResolveConflict with the new winner and item that previously had an unresolved // conflict, so that the correct message will be logged recording that the winner // won and why ResolveConflict(winner, previouslyUnresolvedItem, logUnresolvedConflicts: true); foundConflict(winner, previouslyUnresolvedItem); } _unresolvedConflictItems.Remove(itemKey); } } else if (commitWinner) { _winningItemsByKey[itemKey] = conflictItem; } } }
public static Result ApplySessionChangeSet(this SQLite.SQLiteConnection db, SQLiteChangeSet changeSet, FilterCallback xFilter, ConflictCallback xConflict, object ctx) { return(ChangeSetApply(db, changeSet, xFilter, xConflict, ctx)); }
private static extern Result ChangeSetApply(Sqlite3DatabaseHandle db, int changeSetBufferSize, Sqlite3ChangesetBuffer changeSetBuffer, [MarshalAs(UnmanagedType.FunctionPtr)] FilterCallback xFilter, [MarshalAs(UnmanagedType.FunctionPtr)] ConflictCallback xConflict, IntPtr pCtx);
public void OpenWithManualConflictResolution(string filename, DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { throw new NotImplementedException(mMessage); }
private void InternalOpen(string filename, DataSource source, ConflictResolutionStrategy resolutionStrategy, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { Types.SnapshotConflictPolicy policy; switch (resolutionStrategy) { case ConflictResolutionStrategy.UseLastKnownGood: policy = Types.SnapshotConflictPolicy.LAST_KNOWN_GOOD; break; case ConflictResolutionStrategy.UseMostRecentlySaved: policy = Types.SnapshotConflictPolicy.MOST_RECENTLY_MODIFIED; break; case ConflictResolutionStrategy.UseLongestPlaytime: policy = Types.SnapshotConflictPolicy.LONGEST_PLAYTIME; break; case ConflictResolutionStrategy.UseManual: policy = Types.SnapshotConflictPolicy.MANUAL; break; default: policy = Types.SnapshotConflictPolicy.MOST_RECENTLY_MODIFIED; break; } mSnapshotManager.Open(filename, AsDataSource(source), policy, response => { if (!response.RequestSucceeded()) { completedCallback(AsRequestStatus(response.ResponseStatus()), null); } else if (response.ResponseStatus() == Status.SnapshotOpenStatus.VALID) { completedCallback(SavedGameRequestStatus.Success, response.Data()); } else if (response.ResponseStatus() == Status.SnapshotOpenStatus.VALID_WITH_CONFLICT) { // If we get here, manual conflict resolution is required. NativeSnapshotMetadata original = response.ConflictOriginal(); NativeSnapshotMetadata unmerged = response.ConflictUnmerged(); // Instantiate the conflict resolver. Note that the retry callback closes over // all the parameters we need to retry the open attempt. Once the conflict is // resolved by invoking the appropriate resolution method on // NativeConflictResolver, the resolver will invoke this callback, which will // result in this method being re-executed. This recursion will continue until // all conflicts are resolved or an error occurs. NativeConflictResolver resolver = new NativeConflictResolver( mSnapshotManager, response.ConflictId(), original, unmerged, completedCallback, () => InternalOpen(filename, source, resolutionStrategy, prefetchDataOnConflict, conflictCallback, completedCallback) ); // If we don't have to pre-fetch the saved games' binary data, we can // immediately invoke the conflict callback. Note that this callback is // constructed to execute on the game thread in // OpenWithManualConflictResolution. if (!prefetchDataOnConflict) { conflictCallback(resolver, original, null, unmerged, null); return; } // If we have to prefetch the data, we delegate invoking the conflict resolution // callback to the joiner instance (once both callbacks resolve, the joiner will // invoke the lambda that we declare here, using the fetched data). Prefetcher joiner = new Prefetcher((originalData, unmergedData) => conflictCallback(resolver, original, originalData, unmerged, unmergedData), completedCallback); // Kick off the read calls. mSnapshotManager.Read(original, joiner.OnOriginalDataRead); mSnapshotManager.Read(unmerged, joiner.OnUnmergedDataRead); } else { Logger.e("Unhandled response status"); completedCallback(SavedGameRequestStatus.InternalError, null); } }); }
private void InternalManualOpen(string filename, DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { mSnapshotManager.Open(filename, AsDataSource(source), Types.SnapshotConflictPolicy.MANUAL, delegate(GooglePlayGames.Native.PInvoke.SnapshotManager.OpenResponse response) { if (!response.RequestSucceeded()) { completedCallback(AsRequestStatus(response.ResponseStatus()), null); } else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID) { completedCallback(SavedGameRequestStatus.Success, response.Data()); } else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID_WITH_CONFLICT) { NativeSnapshotMetadata original = response.ConflictOriginal(); NativeSnapshotMetadata unmerged = response.ConflictUnmerged(); NativeConflictResolver resolver = new NativeConflictResolver(mSnapshotManager, response.ConflictId(), original, unmerged, completedCallback, delegate { InternalManualOpen(filename, source, prefetchDataOnConflict, conflictCallback, completedCallback); }); if (!prefetchDataOnConflict) { conflictCallback(resolver, original, null, unmerged, null); } else { Prefetcher @object = new Prefetcher(delegate(byte[] originalData, byte[] unmergedData) { conflictCallback(resolver, original, originalData, unmerged, unmergedData); }, completedCallback); mSnapshotManager.Read(original, @object.OnOriginalDataRead); mSnapshotManager.Read(unmerged, @object.OnUnmergedDataRead); } } else { Logger.e("Unhandled response status"); completedCallback(SavedGameRequestStatus.InternalError, null); } }); }
public void OpenWithManualConflictResolution(string filename, DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action<SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { throw new NotImplementedException(mMessage); }
private void InternalManualOpen(string filename, GooglePlayGames.BasicApi.DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { this.mSnapshotManager.Open(filename, NativeSavedGameClient.AsDataSource(source), Types.SnapshotConflictPolicy.MANUAL, (Action <GooglePlayGames.Native.PInvoke.SnapshotManager.OpenResponse>)(response => { if (!response.RequestSucceeded()) { completedCallback(NativeSavedGameClient.AsRequestStatus(response.ResponseStatus()), (ISavedGameMetadata)null); } else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID) { completedCallback(SavedGameRequestStatus.Success, (ISavedGameMetadata)response.Data()); } else if (response.ResponseStatus() == CommonErrorStatus.SnapshotOpenStatus.VALID_WITH_CONFLICT) { // ISSUE: object of a compiler-generated type is created // ISSUE: variable of a compiler-generated type NativeSavedGameClient.\u003CInternalManualOpen\u003Ec__AnonStorey142.\u003CInternalManualOpen\u003Ec__AnonStorey143 openCAnonStorey143 = new NativeSavedGameClient.\u003CInternalManualOpen\u003Ec__AnonStorey142.\u003CInternalManualOpen\u003Ec__AnonStorey143(); // ISSUE: reference to a compiler-generated field openCAnonStorey143.\u003C\u003Ef__ref\u0024322 = this; // ISSUE: reference to a compiler-generated field openCAnonStorey143.original = response.ConflictOriginal(); // ISSUE: reference to a compiler-generated field openCAnonStorey143.unmerged = response.ConflictUnmerged(); // ISSUE: reference to a compiler-generated field // ISSUE: reference to a compiler-generated field // ISSUE: reference to a compiler-generated field // ISSUE: reference to a compiler-generated method openCAnonStorey143.resolver = new NativeSavedGameClient.NativeConflictResolver(this.mSnapshotManager, response.ConflictId(), openCAnonStorey143.original, openCAnonStorey143.unmerged, completedCallback, new Action(openCAnonStorey143.\u003C\u003Em__6E)); if (!prefetchDataOnConflict) { // ISSUE: reference to a compiler-generated field // ISSUE: reference to a compiler-generated field // ISSUE: reference to a compiler-generated field conflictCallback((IConflictResolver)openCAnonStorey143.resolver, (ISavedGameMetadata)openCAnonStorey143.original, (byte[])null, (ISavedGameMetadata)openCAnonStorey143.unmerged, (byte[])null); } else { // ISSUE: reference to a compiler-generated method NativeSavedGameClient.Prefetcher prefetcher = new NativeSavedGameClient.Prefetcher(new Action <byte[], byte[]>(openCAnonStorey143.\u003C\u003Em__6F), completedCallback); // ISSUE: reference to a compiler-generated field this.mSnapshotManager.Read(openCAnonStorey143.original, new Action <GooglePlayGames.Native.PInvoke.SnapshotManager.ReadResponse>(prefetcher.OnOriginalDataRead)); // ISSUE: reference to a compiler-generated field this.mSnapshotManager.Read(openCAnonStorey143.unmerged, new Action <GooglePlayGames.Native.PInvoke.SnapshotManager.ReadResponse>(prefetcher.OnUnmergedDataRead)); } } else { Logger.e("Unhandled response status"); completedCallback(SavedGameRequestStatus.InternalError, (ISavedGameMetadata)null); } })); }
public void OpenWithManualConflictResolution(string filename, GooglePlayGames.BasicApi.DataSource source, bool prefetchDataOnConflict, ConflictCallback conflictCallback, Action <SavedGameRequestStatus, ISavedGameMetadata> completedCallback) { Misc.CheckNotNull <string>(filename); Misc.CheckNotNull <ConflictCallback>(conflictCallback); Misc.CheckNotNull <Action <SavedGameRequestStatus, ISavedGameMetadata> >(completedCallback); conflictCallback = this.ToOnGameThread(conflictCallback); completedCallback = NativeSavedGameClient.ToOnGameThread <SavedGameRequestStatus, ISavedGameMetadata>(completedCallback); if (!NativeSavedGameClient.IsValidFilename(filename)) { Logger.e("Received invalid filename: " + filename); completedCallback(SavedGameRequestStatus.BadInputError, (ISavedGameMetadata)null); } else { this.InternalManualOpen(filename, source, prefetchDataOnConflict, conflictCallback, completedCallback); } }