Cloud saves
Keeping a copy of a save on a folder or a REST endpoint — local-first, explicit, and never overwriting a copy that changed somewhere else.
The rules, first#
- Local first. Cloud saving is never required. Nothing is uploaded unless something asks for it, and no save ever waits for a network.
- Content, not clocks. A copy is compared with the file this device last uploaded, by fingerprint, so two machines whose clocks disagree cannot confuse it.
- Both changed means a question. The answer is
PK-CLOUD-004and a request for a choice — never a guess. - Nothing lost on failure. A failed or interrupted transfer leaves the local file exactly as it was, and a download is checked before it is allowed to replace anything.
Setting a provider#
Persist.Cloud.Provider = new FolderCloudProvider(class="tok-str">"C:/Users/me/Dropbox/MyGame saves");
var sync = Persist.Cloud.Sync(class="tok-str">"chapter-1");
yield return sync.WaitForCompletion(); // or poll sync.IsDone in Update
if (!sync.Result.Ok)
{
Debug.LogWarning(sync.Result); // the code, what happened, what to try
}| Call | What it does |
|---|---|
Persist.Cloud.Upload(slot) | Sends this device's copy. Refuses if the cloud copy moved since the last upload |
Persist.Cloud.Download(slot) | Brings the cloud copy down. Refuses if this device's copy moved |
Persist.Cloud.Sync(slot) | Does whichever direction cannot lose anything, and reports a conflict when both moved |
Persist.Cloud.Status(slot) | What the two copies look like, changing nothing |
Persist.Cloud.Resolve(slot, choice) | Answers a reported conflict: KeepLocal, KeepRemote or KeepBoth |
Persist.Cloud.FlushPending() | Uploads the slots marked while the cloud could not be reached |
Persist.Cloud.List() / DeleteRemote(slot) | What is stored, and removing a remote copy (never the local one) |
When both copies changed#
Nothing is written. The result carries a CloudConflict a game can show — with both timestamps and sizes — and the choice is explicit:
| Choice | What happens |
|---|---|
CloudChoice.KeepLocal | This device's copy is uploaded, as a conditional write, so a cloud copy that moves again during the upload is refused rather than overwritten |
CloudChoice.KeepRemote | The cloud copy replaces this device's file, after being checked — and the replaced file is kept beside it as a backup |
CloudChoice.KeepBoth | This device's copy is uploaded under a second name first, then the cloud copy is taken. Nothing is overwritten anywhere |
CloudChoice.Ask (the default) decides nothing: it reports the conflict. Persist.Cloud.DefaultChoice can be set if a game would rather have a standing policy, and Persist.Cloud.LastConflict(slot) gives the waiting question to a menu without syncing again.
Offline#
A save made while the cloud cannot be reached is marked as waiting — with Persist.Cloud.Policy = CloudSyncPolicy.QueueAfterSave — and Persist.Cloud.FlushPending() sends it later. No network call ever happens inside a save, because a save has to finish on the frame it was asked for.
Providers#
| Provider | Status | Notes |
|---|---|---|
FolderCloudProvider | Verified | A Dropbox, OneDrive or Google Drive folder, a network share, a USB stick. No account, no SDK, no package. Verified with real files in the package's tests |
RestCloudProvider | Verified | Any endpoint that speaks plain HTTP. Conditional writes through If-Match, metadata through HEAD, and a documented status mapping. Verified over a real socket against a small HTTP server |
MemoryCloudProvider | Available | An in-memory double, for trying a workflow and for tests. Everything in it is gone when the process ends |
| Your own | Available | Implement ISaveCloudProvider (five operations) and PersistKit supplies the comparison, the conflict rules, the queue and the retries |
| Unity Cloud Save, Steam Cloud, Firebase, Supabase | Not supported | No adapter ships. Each needs an account, a plan or a platform SDK, and none could be verified here, so none is claimed. The provider page says exactly what each would take |
What the codes mean#
| Code | What it means |
|---|---|
PK-CLOUD-001 | No provider is set. Your saves are still on this device and untouched |
PK-CLOUD-002 | The cloud could not be reached. Nothing was changed |
PK-CLOUD-003 | The service refused (credentials, permission, quota). Nothing was changed |
PK-CLOUD-004 | Both copies changed. A choice is needed first |
PK-CLOUD-005 | The transfer was cancelled. Nothing was changed |
PK-CLOUD-006 | There is nothing stored remotely under that name |
PK-CLOUD-007 | The download arrived but cannot be used, so the local copy was kept |
PK-CLOUD-008 | This provider does not support that operation |
PK-CLOUD-009 | The transfer took too long and was stopped. Nothing was changed |
Next#
Something wrong on this page? Every page here describes behaviour that is checked in the repository. If a page and the package disagree, the package wins.
Report a documentation problem · Error codes · Platforms and limitations