Easy Chapter Generator
EnglishEspañol

Implement four exact-key operations

IProgressStore has exactly four operations: TryRead(string, out string), Write(string, string), Delete(string), and Flush(). The Manager supplies complete opaque keys. Do not parse, rename, enumerate, prefix again, or combine them with project slot semantics inside the adapter.

A missing key is a normal false read and must set the out value to an empty string. An empty stored string remains distinguishable because TryRead returns true. Null keys throw ArgumentNullException; empty or whitespace keys throw ArgumentException; Write also rejects a null value.

Install the provider before progression locks

Call ChapterManager.TrySetProgressStore(store) before the Manager first reads, writes, hydrates, clears, flushes, or starts progression. Success replaces the backend without copying, deleting, reading, writing, or flushing data. A null provider returns StoreUnavailable; a late call returns ManagerAlreadyInitialized.

An Inspector provider must be a MonoBehaviour implementing IProgressStore. The default when none is assigned is PlayerPrefsProgressStore. Store calls are made on Unity's main thread in V1, so the interface does not require thread safety.

Map backend failures without hiding them

  • Write creates or overwrites one exact string but does not flush.
  • Delete reports whether the exact key existed and does not flush.
  • Flush commits pending backend operations but captures no additional progress.
  • Backend exceptions become ProgressOperationResult.StoreError at the Manager boundary.
  • Unsupported stored schema returns UnsupportedSchema; invalid stable IDs return InvalidIdentifier.

If the backend is asynchronous, buffer exact-key operations synchronously and make Flush define the project-approved durability boundary, or keep persistence outside this V1 adapter. Do not block the main thread on an unbounded network request.

Backend acceptance tests

  1. 1
    Verify missing and empty-string values remain distinguishable.
  2. 2
    Write twice to the same key and confirm the second exact value replaces the first.
  3. 3
    Delete an existing key and a missing key; confirm true then false.
  4. 4
    Confirm Write/Delete do not implicitly flush and one Flush commits the batch.
  5. 5
    Inject read, write, delete, and flush failures and require StoreError from public Manager operations.
  6. 6
    Run the persistence Play Mode scenario and verify no historical events replay after hydration.

Flush progress at a save boundary

Where to place it
Add this method to a project save coordinator or checkpoint controller.
When to call it
Call FlushAtSavePoint after accepted progression writes or before a controlled quit.
Behavior
FlushProgress flushes only the configured progression store; it does not serialize scenes, inventory or arbitrary GameObjects.
using System;
using System.Collections;
using System.Collections.Generic;
using OverFuture.ChapterObjectiveSystem.Chapters;
using OverFuture.ChapterObjectiveSystem.Events;
using OverFuture.ChapterObjectiveSystem.Objectives;
using OverFuture.ChapterObjectiveSystem.Persistence;
using OverFuture.ChapterObjectiveSystem.Progression;
using UnityEngine;

public sealed class FlushProgressExample : MonoBehaviour
{
    public ProgressOperationResult FlushAtSavePoint()
    {
        ChapterManager manager = ChapterManager.Instance;
        return manager != null
            ? manager.FlushProgress()
            : ProgressOperationResult.StoreUnavailable;
    }
}

Implement a custom IProgressStore

Where to place it
Place the store in the project's save assembly and pass an instance to TrySetProgressStore before Manager initialization locks configuration.
When to call it
The Manager calls exact-key operations from Unity's main thread; Flush is called only at explicit boundaries.
Behavior
The in-memory example demonstrates the contract but is not durable. Replace Flush and storage with the project backend without enumerating or rewriting opaque keys.
using System;
using System.Collections;
using System.Collections.Generic;
using OverFuture.ChapterObjectiveSystem.Chapters;
using OverFuture.ChapterObjectiveSystem.Events;
using OverFuture.ChapterObjectiveSystem.Objectives;
using OverFuture.ChapterObjectiveSystem.Persistence;
using OverFuture.ChapterObjectiveSystem.Progression;
using UnityEngine;

public sealed class MemoryProgressStore : IProgressStore
{
    private readonly Dictionary<string, string> values =
        new Dictionary<string, string>(StringComparer.Ordinal);

    public bool TryRead(string key, out string value)
    {
        ValidateKey(key);
        if (values.TryGetValue(key, out value))
        {
            return true;
        }

        value = string.Empty;
        return false;
    }

    public void Write(string key, string value)
    {
        ValidateKey(key);
        values[key] = value ?? throw new ArgumentNullException(nameof(value));
    }

    public bool Delete(string key)
    {
        ValidateKey(key);
        return values.Remove(key);
    }

    public void Flush()
    {
        // Persist pending data in the project-specific backend here.
    }

    private static void ValidateKey(string key)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }

        if (string.IsNullOrWhiteSpace(key))
        {
            throw new ArgumentException("A progress key cannot be empty.", nameof(key));
        }
    }
}