Skip to content

Commit 671feb5

Browse files
Copilotkjy5
andcommitted
Implement file-based save/load for scene state
Co-authored-by: kjy5 <[email protected]>
1 parent 5eb4f26 commit 671feb5

File tree

9 files changed

+208
-3
lines changed

9 files changed

+208
-3
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using Models.Scene;
3+
using Models.Settings;
4+
5+
namespace Models
6+
{
7+
/// <summary>
8+
/// Represents the complete application state for save/load operations.
9+
/// Contains all persistable state slices.
10+
/// </summary>
11+
[Serializable]
12+
public class SavedState
13+
{
14+
public MainState MainState;
15+
public SceneState SceneState;
16+
public SettingsState SettingsState;
17+
public RigState RigState;
18+
public AtlasSettingsState AtlasSettingsState;
19+
}
20+
}

Assets/Scripts/Models/SavedState.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.IO;
3+
using UnityEngine;
4+
5+
namespace Services
6+
{
7+
/// <summary>
8+
/// Provides methods to save and load data to/from local JSON files.
9+
/// </summary>
10+
public class FileStorageService
11+
{
12+
/// <summary>
13+
/// Saves a value of type <typeparamref name="T"/> to a JSON file.
14+
/// </summary>
15+
/// <typeparam name="T">The type of the value to save.</typeparam>
16+
/// <param name="filePath">The full file path where the JSON file will be saved.</param>
17+
/// <param name="value">The value to save.</param>
18+
/// <returns>True if the save was successful, false otherwise.</returns>
19+
public bool SaveToFile<T>(string filePath, T value)
20+
{
21+
try
22+
{
23+
var json = JsonUtility.ToJson(value, true);
24+
File.WriteAllText(filePath, json);
25+
Debug.Log($"Saved state to: {filePath}");
26+
return true;
27+
}
28+
catch (Exception e)
29+
{
30+
Debug.LogError($"Failed to save to file {filePath}: {e.Message}");
31+
return false;
32+
}
33+
}
34+
35+
/// <summary>
36+
/// Loads a value of type <typeparamref name="T"/> from a JSON file.
37+
/// </summary>
38+
/// <typeparam name="T">The type of the value to load.</typeparam>
39+
/// <param name="filePath">The full file path to the JSON file to load.</param>
40+
/// <param name="result">The loaded value, or default if loading failed.</param>
41+
/// <returns>True if the load was successful, false otherwise.</returns>
42+
public bool LoadFromFile<T>(string filePath, out T result) where T : new()
43+
{
44+
result = default;
45+
46+
try
47+
{
48+
if (!File.Exists(filePath))
49+
{
50+
Debug.LogWarning($"File not found: {filePath}");
51+
return false;
52+
}
53+
54+
var json = File.ReadAllText(filePath);
55+
result = JsonUtility.FromJson<T>(json);
56+
Debug.Log($"Loaded state from: {filePath}");
57+
return true;
58+
}
59+
catch (Exception e)
60+
{
61+
Debug.LogError($"Failed to load from file {filePath}: {e.Message}");
62+
return false;
63+
}
64+
}
65+
}
66+
}

Assets/Scripts/Services/FileStorageService.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/Scripts/Services/StoreService.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace Services
1212
public class StoreService
1313
{
1414
private readonly LocalStorageService _localStorageService;
15+
private readonly FileStorageService _fileStorageService;
1516

1617
/// <summary>
1718
/// Gets the Redux store instance for partitioned application state.
@@ -23,9 +24,11 @@ public class StoreService
2324
/// Loads initial state from local storage and configures the Redux store.
2425
/// </summary>
2526
/// <param name="localStorageService">The local storage service for state persistence.</param>
26-
public StoreService(LocalStorageService localStorageService)
27+
/// <param name="fileStorageService">The file storage service for save/load operations.</param>
28+
public StoreService(LocalStorageService localStorageService, FileStorageService fileStorageService)
2729
{
2830
_localStorageService = localStorageService;
31+
_fileStorageService = fileStorageService;
2932

3033
// Initialize state in memory.
3134
var initialMainState = _localStorageService.GetValue(
@@ -359,5 +362,48 @@ public void Save()
359362
Store.GetState<AtlasSettingsState>(SliceNames.ATLAS_SETTINGS_SLICE)
360363
);
361364
}
365+
366+
/// <summary>
367+
/// Saves the current state to a JSON file.
368+
/// </summary>
369+
/// <param name="filePath">The full file path where to save the state.</param>
370+
/// <returns>True if the save was successful, false otherwise.</returns>
371+
public bool SaveToFile(string filePath)
372+
{
373+
var savedState = new SavedState
374+
{
375+
MainState = Store.GetState<MainState>(SliceNames.MAIN_SLICE),
376+
SceneState = Store.GetState<SceneState>(SliceNames.SCENE_SLICE),
377+
SettingsState = Store.GetState<SettingsState>(SliceNames.SETTINGS_SLICE),
378+
RigState = Store.GetState<RigState>(SliceNames.RIG_SLICE),
379+
AtlasSettingsState = Store.GetState<AtlasSettingsState>(SliceNames.ATLAS_SETTINGS_SLICE)
380+
};
381+
382+
return _fileStorageService.SaveToFile(filePath, savedState);
383+
}
384+
385+
/// <summary>
386+
/// Loads state from a JSON file and updates both the store and local storage.
387+
/// </summary>
388+
/// <param name="filePath">The full file path from which to load the state.</param>
389+
/// <returns>True if the load was successful, false otherwise.</returns>
390+
public bool LoadFromFile(string filePath)
391+
{
392+
if (!_fileStorageService.LoadFromFile<SavedState>(filePath, out var savedState))
393+
return false;
394+
395+
// Update local storage with the loaded state.
396+
_localStorageService.SetValue(SliceNames.MAIN_SLICE, savedState.MainState);
397+
_localStorageService.SetValue(SliceNames.SCENE_SLICE, savedState.SceneState);
398+
_localStorageService.SetValue(SliceNames.SETTINGS_SLICE, savedState.SettingsState);
399+
_localStorageService.SetValue(SliceNames.RIG_SLICE, savedState.RigState);
400+
_localStorageService.SetValue(SliceNames.ATLAS_SETTINGS_SLICE, savedState.AtlasSettingsState);
401+
402+
// TODO: Update the store with the loaded state
403+
// This will require dispatching actions to update each slice
404+
// For now, we've updated local storage and will need to reload the app
405+
406+
return true;
407+
}
362408
}
363409
}

Assets/Scripts/UI/PinpointAppBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ protected override void OnConfiguringApp(AppBuilder builder)
2929

3030
// Services.
3131
builder.services.AddSingleton<LocalStorageService>();
32+
builder.services.AddSingleton<FileStorageService>();
3233
builder.services.AddSingleton<StoreService>();
3334
builder.services.AddSingleton<ProbeService>();
3435
builder.services.AddSingleton<EphysLinkService>();

Assets/Scripts/UI/ViewModels/MainViewModel.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,66 @@ private void SetLeftSidePanelTabIndex(int index)
125125
_storeService.Store.Dispatch(MainActions.SET_LEFT_SIDE_PANEL_TAB_INDEX, index);
126126
}
127127

128+
[ICommand]
129+
private void SaveStateToFile()
130+
{
131+
#if UNITY_EDITOR
132+
var filePath = UnityEditor.EditorUtility.SaveFilePanel(
133+
"Save Scene State",
134+
UnityEngine.Application.persistentDataPath,
135+
"scene_state.json",
136+
"json"
137+
);
138+
#else
139+
var filePath = System.IO.Path.Combine(
140+
UnityEngine.Application.persistentDataPath,
141+
"scene_state.json"
142+
);
143+
#endif
144+
145+
if (!string.IsNullOrEmpty(filePath))
146+
{
147+
if (_storeService.SaveToFile(filePath))
148+
{
149+
Debug.Log($"Scene state saved successfully to: {filePath}");
150+
}
151+
else
152+
{
153+
Debug.LogError("Failed to save scene state");
154+
}
155+
}
156+
}
157+
158+
[ICommand]
159+
private void LoadStateFromFile()
160+
{
161+
#if UNITY_EDITOR
162+
var filePath = UnityEditor.EditorUtility.OpenFilePanel(
163+
"Load Scene State",
164+
UnityEngine.Application.persistentDataPath,
165+
"json"
166+
);
167+
#else
168+
var filePath = System.IO.Path.Combine(
169+
UnityEngine.Application.persistentDataPath,
170+
"scene_state.json"
171+
);
172+
#endif
173+
174+
if (!string.IsNullOrEmpty(filePath))
175+
{
176+
if (_storeService.LoadFromFile(filePath))
177+
{
178+
Debug.Log($"Scene state loaded successfully from: {filePath}");
179+
Debug.Log("Please reload the application to see the loaded state");
180+
}
181+
else
182+
{
183+
Debug.LogError("Failed to load scene state");
184+
}
185+
}
186+
}
187+
128188
#endregion
129189
}
130190
}

Assets/Scripts/UI/Views/MainView.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public MainView(MainViewModel mainViewModel)
4545
var leftSidePanelCollapseButton = root.Q<Button>("left-side-panel__collapse-button");
4646
var rightSidePanelCollapseButton = root.Q<Button>("right-side-panel__collapse-button");
4747
var leftSidePanelTabs = root.Q<Tabs>("left-side-panel__tabs");
48+
var saveMenuItem = root.Q<Unity.AppUI.UI.MenuItem>("file-menu__save");
49+
var loadMenuItem = root.Q<Unity.AppUI.UI.MenuItem>("file-menu__load");
4850

4951
// Initialize subviews.
5052
_ = PinpointApp.Services.GetRequiredService<SceneView>();
@@ -80,6 +82,10 @@ public MainView(MainViewModel mainViewModel)
8082
leftSidePanelTabs.RegisterValueChangedCallback(evt =>
8183
mainViewModel.SetLeftSidePanelTabIndexCommand.Execute(evt.newValue)
8284
);
85+
saveMenuItem.clickable.clicked += () =>
86+
mainViewModel.SaveStateToFileCommand.Execute(null);
87+
loadMenuItem.clickable.clicked += () =>
88+
mainViewModel.LoadStateFromFileCommand.Execute(null);
8389

8490
// Initialize view from view model state.
8591
mainSplitView.RestoreState(mainViewModel.MainSplitViewState);

Assets/UI/Main.uxml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
<Unity.AppUI.UI.MenuTrigger>
1616
<Unity.AppUI.UI.ActionButton label="File" icon="file" />
1717
<Unity.AppUI.UI.Menu>
18-
<Unity.AppUI.UI.MenuItem label="Save" />
19-
<Unity.AppUI.UI.MenuItem label="Load" />
18+
<Unity.AppUI.UI.MenuItem label="Save" name="file-menu__save" />
19+
<Unity.AppUI.UI.MenuItem label="Load" name="file-menu__load" />
2020
</Unity.AppUI.UI.Menu>
2121
</Unity.AppUI.UI.MenuTrigger>
2222
<Unity.AppUI.UI.Spacer />

0 commit comments

Comments
 (0)