Writing a cloud provider

The ISaveCloudProvider contract — five operations, honest capabilities, poll-based requests, and what PersistKit does above it so a provider does not have to.

Cloud NexusForge Persistence 1.0.0 Updated

What a provider has to do#

csharp
public interface ISaveCloudProvider
{
    string Name { get; }
    CloudCapabilities Capabilities { get; }
    CloudAccountState Account { get; }
    string AccountMessage { get; }

    CloudConnectRequest Connect();
    CloudUploadRequest Upload(CloudUploadSpec spec);
    CloudDownloadRequest Download(string remoteKey);
    CloudListRequest List();
    CloudDeleteRequest Delete(string remoteKey);
}

Five operations, and nothing about saves in any of them: bytes out, bytes in. Everything a save system has to get right — deciding which copy wins, refusing to overwrite a copy that moved, remembering what was uploaded, queueing an offline save, retrying — is above the seam and is the same for every provider.

Requests are polled, not blocked on#

PersistKit's runtime does no work off the main thread, so an operation has to make progress while the game runs:

csharp
public CloudDownloadRequest Download(string remoteKey)
{
    var request = new CloudDownloadRequest(remoteKey);

    request.Attach(r =>                       // one step, called whenever the request is read
    {
        if (r.CancelRequested) { r.CompleteAsCancelled(class="tok-str">"the download"); return false; }
class="tok-com">
        // …start your request, then complete when it is done:
        r.Complete(PersistResult.Success(true));
        return true;
    });

    return request;
}

Attach, Complete, CompleteAsCancelled and ReportProgress are public for exactly this reason. A game uses IsDone, Progress, Result, Cancel() and yield return request.WaitForCompletion().

Four rules that make a provider a good citizen#

  1. Never throw for an expected failure. A network problem is a PersistResult with a code. A thrown exception inside a provider is treated as a defect.
  2. Declare your capabilities honestly. CloudCapabilities.ConditionalWrite is what makes a lost race a reported conflict instead of a silent overwrite. Claiming it without doing it is how one device destroys another's save.
  3. Report metadata when you can by implementing ISaveCloudMetadataProvider, so a sync compares two copies without downloading one to find out what it is.
  4. Write in a way that cannot half-replace a save. The folder provider writes a .part file and promotes it; the REST provider uses If-Match so the endpoint refuses a stale write.

What each service would take#

ServiceWhat it needsWhere it would plug in
A folder that syncs (Dropbox, OneDrive, a share)NothingShipped: FolderCloudProvider
Your own HTTP endpointA URL, and a place to put a tokenShipped: RestCloudProvider
Unity Cloud Save (UGS)A Unity project ID, the com.unity.services.cloudsave package and a signed-in playerA provider that resolves the player id in Connect() and calls the SDK's save/load — response headers map onto the same codes
Steam CloudThe Steamworks SDK, a Steam App ID, and a signed-in clientA provider over ISteamRemoteStorage: write/read/delete per file, with FileWriteAsync's progress reported through ReportProgress
Firebase, SupabaseA project, credentials, and a table or bucketThese are REST APIs, so RestCloudProvider with BaseUrl, an auth header and (for listing) ListUrlTemplate reaches them today. The credentials and the rules are the work
PlayFabA title ID and player authenticationA provider over its file API, with EntityId as the account

Not verified Those four are described, not implemented. None could be exercised here — each needs an account, a plan or a platform SDK — so no claim is made for any of them, and nothing in the package references their SDKs.

Testing a provider without a network#

MemoryCloudProvider is the double the package's own cloud behaviour is verified against: it can be offline, made slow, made to refuse, and shared between two instances so that "the same save on two devices" is a real test. That verifies PersistKit, not your service: a provider still has to be tested against the real thing.

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