Skip to content

Commit 597357d

Browse files
committed
Added scope-aware file routing (Global vs Slot) via ISaveable<TState>.Scope
1 parent 947f33e commit 597357d

File tree

7 files changed

+50
-17
lines changed

7 files changed

+50
-17
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## [0.14.0] – 2025-10-27
4+
5+
### Breaking Change: Scoped file routing
6+
7+
- `ISaveable<TState>` now declares `StorageScope` Scope (Global or Slot), making pathing explicit and removing the need to toggle `SaveManager.SaveSlotIndex` just to “ignore” slots.
8+
- Scope remembered at registration: `SaveManager` records a filename scope mapping when `RegisterSaveable(...)` is called and uses it for path resolution. (Internal change; no new public API.)
9+
- `FileHandler` pathing updated: `GetPartialPath()` now prefixes slot{N}/ only for Slot‑scoped files. Attempting to save/load a Slot‑scoped file with no selected slot throws a clear error. (Prevents global files like Options from accidentally ending up in a slot folder.)
10+
11+
### Migration notes
12+
13+
- Add `StorageScope Scope { get; }` to every `ISaveable<TState>`. Use Global for shared data (e.g., options/settings) and Slot for per‑profile data.
14+
- Remove any code that temporarily sets SaveManager.SaveSlotIndex = -1 around global saves; it’s no longer necessary.
15+
316
## [0.13.0] – 2025-09-22
417

518
### Breaking Change

Runtime/FileHandler.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,19 @@ protected string GetPartialPath(string pathOrFilename)
6767

6868
string path = $"{pathOrFilename}{FilenameSuffix}{FileExtension}";
6969

70-
if (SaveManager.SaveSlotIndex > -1)
70+
var scope = SaveManager.ResolveScopeFor(pathOrFilename);
71+
72+
if (scope == StorageScope.Slot)
73+
{
74+
if (SaveManager.SaveSlotIndex < 0)
75+
throw new InvalidOperationException(
76+
"[Save Async] Slot-scoped file requested but SaveSlotIndex is not set. " +
77+
"Set SaveManager.SaveSlotIndex before saving/loading slot-scoped files.");
78+
7179
return Path.Combine($"slot{SaveManager.SaveSlotIndex}", path);
80+
}
7281

82+
// If the scope is global, just return the path as-is
7383
return path;
7484
}
7585

Runtime/ISaveable.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Buck.SaveAsync
44
{
5+
public enum StorageScope { Global, Slot }
6+
57
/// <summary>
68
/// Allows an object to be saved and loaded via the SaveManager class using a strongly-typed state.
79
/// Implementations should define a serializable struct or class for TState.
@@ -23,6 +25,11 @@ public interface ISaveable<TState>
2325
/// </summary>
2426
string Filename { get; }
2527

28+
/// <summary>
29+
/// This indicates whether the data is saved globally or per save slot.
30+
/// </summary>
31+
StorageScope Scope { get; }
32+
2633
/// <summary>
2734
/// This is the current version of this ISaveable's data structure.
2835
/// If you change the TState structure, increment this version number.

Runtime/SaveManager.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ sealed class LoadedSaveable
110110
static readonly Dictionary<string, IBoxedSaveable> m_saveables = new();
111111
static readonly List<LoadedSaveable> m_loadedSaveables = new();
112112
static readonly Queue<FileOperation> m_fileOperationQueue = new();
113+
static readonly Dictionary<string, StorageScope> s_fileScopes = new();
113114

114115
static readonly object s_QueueLock = new();
115116
static bool m_initialized;
@@ -174,20 +175,22 @@ public static void RegisterSaveable<TState>(ISaveable<TState> saveable)
174175
var boxed = new BoxedSaveable<TState>(saveable);
175176
if (!m_saveables.TryAdd(boxed.Key, boxed))
176177
Debug.LogWarning($"[Save Async] SaveManager.RegisterSaveable() - Saveable with Key \"{boxed.Key}\" already exists.");
178+
179+
var scope = saveable.Scope;
180+
if (s_fileScopes.TryGetValue(boxed.Filename, out var existing) && existing != scope)
181+
Debug.LogError($"[Save Async] Conflicting scopes for filename \"{boxed.Filename}\": {existing} vs {scope}.");
182+
else
183+
s_fileScopes[boxed.Filename] = scope;
177184
}
178-
179-
/// <summary>
180-
/// Unregisters a previously registered ISaveable by key. This is useful when unloading scenes.
181-
/// </summary>
182-
/// <param name="key">The unique key of the ISaveable to unregister.</param>
183-
public static void UnregisterSaveable(string key)
185+
186+
internal static StorageScope ResolveScopeFor(string filename)
184187
{
185-
Initialize();
186-
187-
if (string.IsNullOrEmpty(key))
188-
return;
188+
if (string.IsNullOrEmpty(filename))
189+
return StorageScope.Slot; // safest default
189190

190-
m_saveables.Remove(key);
191+
return s_fileScopes.TryGetValue(filename, out var scope)
192+
? scope
193+
: StorageScope.Slot; // default Slot unless explicitly registered as Global
191194
}
192195

193196
/// <summary>

Samples~/SaveAsyncExample/CacheDataExample.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ public class CacheDataExample : MonoBehaviour, ISaveable<CacheDataExample.MySave
2525
void OnValidate() => SaveManager.GetSerializableGuid(ref m_guidBytes);
2626

2727
public string Filename => Files.SomeFile;
28-
29-
public int Version => 1;
28+
public StorageScope Scope => StorageScope.Global;
29+
public int Version => 0;
3030

3131
[Serializable]
3232
public struct MySaveData

Samples~/SaveAsyncExample/GameDataExample.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public class GameDataExample : MonoBehaviour, ISaveable<GameDataExample.MyCustom
1919
// For an example that uses a Guid, see CacheDataExample.cs.
2020
public string Key => "GameDataExample";
2121
public string Filename => Files.GameData;
22-
23-
public int Version => 1;
22+
public StorageScope Scope => StorageScope.Global;
23+
public int Version => 0;
2424

2525
[Serializable]
2626
public struct Item

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "co.buck.saveasync",
3-
"version": "0.13.0",
3+
"version": "0.14.0",
44
"displayName": "Save Async",
55
"description": "Save Async is BUCK's Unity package for asynchronously saving and loading data in the background using Unity's Awaitable class. Capture and restore state without interrupting Unity's main render thread.",
66
"unity": "2023.1",

0 commit comments

Comments
 (0)