From 5eb4f26d8b974c65bfdccf031d401ba9da27e262 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 07:02:33 +0000 Subject: [PATCH 1/6] Initial plan From 671feb5365294fa3bcc8a92c1b09a7e91e42c681 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 07:09:18 +0000 Subject: [PATCH 2/6] Implement file-based save/load for scene state Co-authored-by: kjy5 <82800265+kjy5@users.noreply.github.com> --- Assets/Scripts/Models/SavedState.cs | 20 ++++++ Assets/Scripts/Models/SavedState.cs.meta | 3 + Assets/Scripts/Services/FileStorageService.cs | 66 +++++++++++++++++++ .../Services/FileStorageService.cs.meta | 3 + Assets/Scripts/Services/StoreService.cs | 48 +++++++++++++- Assets/Scripts/UI/PinpointAppBuilder.cs | 1 + Assets/Scripts/UI/ViewModels/MainViewModel.cs | 60 +++++++++++++++++ Assets/Scripts/UI/Views/MainView.cs | 6 ++ Assets/UI/Main.uxml | 4 +- 9 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 Assets/Scripts/Models/SavedState.cs create mode 100644 Assets/Scripts/Models/SavedState.cs.meta create mode 100644 Assets/Scripts/Services/FileStorageService.cs create mode 100644 Assets/Scripts/Services/FileStorageService.cs.meta diff --git a/Assets/Scripts/Models/SavedState.cs b/Assets/Scripts/Models/SavedState.cs new file mode 100644 index 00000000..096ece44 --- /dev/null +++ b/Assets/Scripts/Models/SavedState.cs @@ -0,0 +1,20 @@ +using System; +using Models.Scene; +using Models.Settings; + +namespace Models +{ + /// + /// Represents the complete application state for save/load operations. + /// Contains all persistable state slices. + /// + [Serializable] + public class SavedState + { + public MainState MainState; + public SceneState SceneState; + public SettingsState SettingsState; + public RigState RigState; + public AtlasSettingsState AtlasSettingsState; + } +} diff --git a/Assets/Scripts/Models/SavedState.cs.meta b/Assets/Scripts/Models/SavedState.cs.meta new file mode 100644 index 00000000..b3bc0362 --- /dev/null +++ b/Assets/Scripts/Models/SavedState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 36f0346ffa4b4f8bb620fd7463ad3877 +timeCreated: 1750445616 diff --git a/Assets/Scripts/Services/FileStorageService.cs b/Assets/Scripts/Services/FileStorageService.cs new file mode 100644 index 00000000..46accf28 --- /dev/null +++ b/Assets/Scripts/Services/FileStorageService.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using UnityEngine; + +namespace Services +{ + /// + /// Provides methods to save and load data to/from local JSON files. + /// + public class FileStorageService + { + /// + /// Saves a value of type to a JSON file. + /// + /// The type of the value to save. + /// The full file path where the JSON file will be saved. + /// The value to save. + /// True if the save was successful, false otherwise. + public bool SaveToFile(string filePath, T value) + { + try + { + var json = JsonUtility.ToJson(value, true); + File.WriteAllText(filePath, json); + Debug.Log($"Saved state to: {filePath}"); + return true; + } + catch (Exception e) + { + Debug.LogError($"Failed to save to file {filePath}: {e.Message}"); + return false; + } + } + + /// + /// Loads a value of type from a JSON file. + /// + /// The type of the value to load. + /// The full file path to the JSON file to load. + /// The loaded value, or default if loading failed. + /// True if the load was successful, false otherwise. + public bool LoadFromFile(string filePath, out T result) where T : new() + { + result = default; + + try + { + if (!File.Exists(filePath)) + { + Debug.LogWarning($"File not found: {filePath}"); + return false; + } + + var json = File.ReadAllText(filePath); + result = JsonUtility.FromJson(json); + Debug.Log($"Loaded state from: {filePath}"); + return true; + } + catch (Exception e) + { + Debug.LogError($"Failed to load from file {filePath}: {e.Message}"); + return false; + } + } + } +} diff --git a/Assets/Scripts/Services/FileStorageService.cs.meta b/Assets/Scripts/Services/FileStorageService.cs.meta new file mode 100644 index 00000000..4dbc8e90 --- /dev/null +++ b/Assets/Scripts/Services/FileStorageService.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cfba2e595bc7443891a0ba82a9d49f61 +timeCreated: 1750445616 diff --git a/Assets/Scripts/Services/StoreService.cs b/Assets/Scripts/Services/StoreService.cs index 0b3244f6..1b5004c1 100644 --- a/Assets/Scripts/Services/StoreService.cs +++ b/Assets/Scripts/Services/StoreService.cs @@ -12,6 +12,7 @@ namespace Services public class StoreService { private readonly LocalStorageService _localStorageService; + private readonly FileStorageService _fileStorageService; /// /// Gets the Redux store instance for partitioned application state. @@ -23,9 +24,11 @@ public class StoreService /// Loads initial state from local storage and configures the Redux store. /// /// The local storage service for state persistence. - public StoreService(LocalStorageService localStorageService) + /// The file storage service for save/load operations. + public StoreService(LocalStorageService localStorageService, FileStorageService fileStorageService) { _localStorageService = localStorageService; + _fileStorageService = fileStorageService; // Initialize state in memory. var initialMainState = _localStorageService.GetValue( @@ -359,5 +362,48 @@ public void Save() Store.GetState(SliceNames.ATLAS_SETTINGS_SLICE) ); } + + /// + /// Saves the current state to a JSON file. + /// + /// The full file path where to save the state. + /// True if the save was successful, false otherwise. + public bool SaveToFile(string filePath) + { + var savedState = new SavedState + { + MainState = Store.GetState(SliceNames.MAIN_SLICE), + SceneState = Store.GetState(SliceNames.SCENE_SLICE), + SettingsState = Store.GetState(SliceNames.SETTINGS_SLICE), + RigState = Store.GetState(SliceNames.RIG_SLICE), + AtlasSettingsState = Store.GetState(SliceNames.ATLAS_SETTINGS_SLICE) + }; + + return _fileStorageService.SaveToFile(filePath, savedState); + } + + /// + /// Loads state from a JSON file and updates both the store and local storage. + /// + /// The full file path from which to load the state. + /// True if the load was successful, false otherwise. + public bool LoadFromFile(string filePath) + { + if (!_fileStorageService.LoadFromFile(filePath, out var savedState)) + return false; + + // Update local storage with the loaded state. + _localStorageService.SetValue(SliceNames.MAIN_SLICE, savedState.MainState); + _localStorageService.SetValue(SliceNames.SCENE_SLICE, savedState.SceneState); + _localStorageService.SetValue(SliceNames.SETTINGS_SLICE, savedState.SettingsState); + _localStorageService.SetValue(SliceNames.RIG_SLICE, savedState.RigState); + _localStorageService.SetValue(SliceNames.ATLAS_SETTINGS_SLICE, savedState.AtlasSettingsState); + + // TODO: Update the store with the loaded state + // This will require dispatching actions to update each slice + // For now, we've updated local storage and will need to reload the app + + return true; + } } } diff --git a/Assets/Scripts/UI/PinpointAppBuilder.cs b/Assets/Scripts/UI/PinpointAppBuilder.cs index e4e1dc08..e199bd8e 100644 --- a/Assets/Scripts/UI/PinpointAppBuilder.cs +++ b/Assets/Scripts/UI/PinpointAppBuilder.cs @@ -29,6 +29,7 @@ protected override void OnConfiguringApp(AppBuilder builder) // Services. builder.services.AddSingleton(); + builder.services.AddSingleton(); builder.services.AddSingleton(); builder.services.AddSingleton(); builder.services.AddSingleton(); diff --git a/Assets/Scripts/UI/ViewModels/MainViewModel.cs b/Assets/Scripts/UI/ViewModels/MainViewModel.cs index 11a413e6..aa36a376 100644 --- a/Assets/Scripts/UI/ViewModels/MainViewModel.cs +++ b/Assets/Scripts/UI/ViewModels/MainViewModel.cs @@ -125,6 +125,66 @@ private void SetLeftSidePanelTabIndex(int index) _storeService.Store.Dispatch(MainActions.SET_LEFT_SIDE_PANEL_TAB_INDEX, index); } + [ICommand] + private void SaveStateToFile() + { +#if UNITY_EDITOR + var filePath = UnityEditor.EditorUtility.SaveFilePanel( + "Save Scene State", + UnityEngine.Application.persistentDataPath, + "scene_state.json", + "json" + ); +#else + var filePath = System.IO.Path.Combine( + UnityEngine.Application.persistentDataPath, + "scene_state.json" + ); +#endif + + if (!string.IsNullOrEmpty(filePath)) + { + if (_storeService.SaveToFile(filePath)) + { + Debug.Log($"Scene state saved successfully to: {filePath}"); + } + else + { + Debug.LogError("Failed to save scene state"); + } + } + } + + [ICommand] + private void LoadStateFromFile() + { +#if UNITY_EDITOR + var filePath = UnityEditor.EditorUtility.OpenFilePanel( + "Load Scene State", + UnityEngine.Application.persistentDataPath, + "json" + ); +#else + var filePath = System.IO.Path.Combine( + UnityEngine.Application.persistentDataPath, + "scene_state.json" + ); +#endif + + if (!string.IsNullOrEmpty(filePath)) + { + if (_storeService.LoadFromFile(filePath)) + { + Debug.Log($"Scene state loaded successfully from: {filePath}"); + Debug.Log("Please reload the application to see the loaded state"); + } + else + { + Debug.LogError("Failed to load scene state"); + } + } + } + #endregion } } diff --git a/Assets/Scripts/UI/Views/MainView.cs b/Assets/Scripts/UI/Views/MainView.cs index 11d908f1..bab709bf 100644 --- a/Assets/Scripts/UI/Views/MainView.cs +++ b/Assets/Scripts/UI/Views/MainView.cs @@ -45,6 +45,8 @@ public MainView(MainViewModel mainViewModel) var leftSidePanelCollapseButton = root.Q