Working synchronous Addressables settings loading

This commit is contained in:
Michal Pikulski
2025-09-23 15:30:21 +02:00
parent 197aedddb0
commit 404f6f4e5c
4 changed files with 97 additions and 75 deletions

View File

@@ -10,7 +10,6 @@ namespace AppleHills.Editor
public class SettingsMigrationWindow : EditorWindow public class SettingsMigrationWindow : EditorWindow
{ {
private GameSettings legacySettings; private GameSettings legacySettings;
private bool settingsFolderExists;
private bool addressablesInstalled; private bool addressablesInstalled;
private AddressableAssetSettings addressableSettings; private AddressableAssetSettings addressableSettings;
private AddressableAssetGroup settingsGroup; private AddressableAssetGroup settingsGroup;
@@ -19,7 +18,7 @@ namespace AppleHills.Editor
private Vector2 scrollPosition; private Vector2 scrollPosition;
private bool migrationCompleted = false; private bool migrationCompleted = false;
[MenuItem("AppleHills/Migrate Legacy Settings")] [MenuItem("Tools/Migrate Legacy Settings")]
public static void ShowWindow() public static void ShowWindow()
{ {
var window = GetWindow<SettingsMigrationWindow>("Settings Migration"); var window = GetWindow<SettingsMigrationWindow>("Settings Migration");
@@ -28,9 +27,6 @@ namespace AppleHills.Editor
private void OnEnable() private void OnEnable()
{ {
// Check if Settings folder exists
settingsFolderExists = AssetDatabase.IsValidFolder("Assets/Settings");
// Check if Addressables package is installed // Check if Addressables package is installed
addressablesInstalled = AddressableAssetSettingsDefaultObject.SettingsExists; addressablesInstalled = AddressableAssetSettingsDefaultObject.SettingsExists;
@@ -60,8 +56,7 @@ namespace AppleHills.Editor
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox(
"This tool will migrate your legacy GameSettings to the new modular settings system. " + "This tool will migrate your legacy GameSettings to the new modular settings system. " +
"It will create new settings assets in the Assets/Settings folder, mark them as Addressables, " + "It will create new settings assets and mark them as Addressables for synchronous loading at runtime.",
"and copy values from your legacy settings.",
MessageType.Info); MessageType.Info);
EditorGUILayout.Space(10); EditorGUILayout.Space(10);
@@ -112,7 +107,8 @@ namespace AppleHills.Editor
EditorGUILayout.LabelField("Migration completed successfully!", successStyle); EditorGUILayout.LabelField("Migration completed successfully!", successStyle);
EditorGUILayout.HelpBox( EditorGUILayout.HelpBox(
"The legacy settings have been migrated to the new system. " + "The legacy settings have been migrated to the new system. " +
"You can now access these settings through the AppleHills > Settings Editor menu.", "You can now access these settings through the AppleHills > Settings Editor menu. " +
"Settings are marked as Addressables and will load synchronously at runtime.",
MessageType.Info); MessageType.Info);
} }
@@ -122,10 +118,9 @@ namespace AppleHills.Editor
private void MigrateSettings() private void MigrateSettings()
{ {
// Create Settings folder if it doesn't exist // Create Settings folder if it doesn't exist
if (!settingsFolderExists) if (!AssetDatabase.IsValidFolder("Assets/Settings"))
{ {
AssetDatabase.CreateFolder("Assets", "Settings"); AssetDatabase.CreateFolder("Assets", "Settings");
settingsFolderExists = true;
} }
// Setup Addressables group for settings // Setup Addressables group for settings
@@ -159,7 +154,7 @@ namespace AppleHills.Editor
var guid = AssetDatabase.AssetPathToGUID(assetPath); var guid = AssetDatabase.AssetPathToGUID(assetPath);
var entry = addressableSettings.CreateOrMoveEntry(guid, settingsGroup); var entry = addressableSettings.CreateOrMoveEntry(guid, settingsGroup);
// Set the address // Set the address - use without .asset extension for cleaner addresses
entry.address = address; entry.address = address;
Debug.Log($"Added {assetPath} to Addressables with address {address}"); Debug.Log($"Added {assetPath} to Addressables with address {address}");

View File

@@ -1979,6 +1979,10 @@ PrefabInstance:
propertyPath: m_LocalEulerAnglesHint.z propertyPath: m_LocalEulerAnglesHint.z
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 7852204877518954380, guid: 8ac0210dbf9d7754e9526d6d5c214f49, type: 3}
propertyPath: maxSpeed
value: 15
objectReference: {fileID: 0}
m_RemovedComponents: [] m_RemovedComponents: []
m_RemovedGameObjects: [] m_RemovedGameObjects: []
m_AddedGameObjects: [] m_AddedGameObjects: []

View File

@@ -1,6 +1,6 @@
using UnityEngine; using UnityEngine;
using AppleHills.Core.Settings; using AppleHills.Core.Settings;
using System.Collections; using System;
/// <summary> /// <summary>
/// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters. /// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters.
@@ -37,59 +37,88 @@ public class GameManager : MonoBehaviour
[Header("Settings Status")] [Header("Settings Status")]
[SerializeField] private bool _settingsLoaded = false; [SerializeField] private bool _settingsLoaded = false;
public bool SettingsLoaded => _settingsLoaded;
// Use this for fallback values when settings aren't loaded yet
[Header("Fallback Settings")]
[SerializeField] private GameSettings fallbackSettings;
void Awake() void Awake()
{ {
_instance = this; _instance = this;
// If no fallback settings assigned, try to load them
if (fallbackSettings == null)
{
fallbackSettings = Resources.Load<GameSettings>("DefaultSettings");
}
// Create settings provider if it doesn't exist // Create settings provider if it doesn't exist
SettingsProvider.Instance.gameObject.name = "Settings Provider"; SettingsProvider.Instance.gameObject.name = "Settings Provider";
// Load all settings // Load all settings synchronously during Awake
StartCoroutine(InitializeSettings()); InitializeSettings();
// DontDestroyOnLoad(gameObject); // DontDestroyOnLoad(gameObject);
} }
private IEnumerator InitializeSettings() private void InitializeSettings()
{ {
// Initialize the settings provider Debug.Log("Starting settings initialization...");
var initComplete = false;
SettingsProvider.Instance.PreloadAllSettings(() => initComplete = true); // Load settings synchronously
var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous<PlayerFollowerSettings>();
var interactionSettings = SettingsProvider.Instance.LoadSettingsSynchronous<InteractionSettings>();
var minigameSettings = SettingsProvider.Instance.LoadSettingsSynchronous<MinigameSettings>();
// Wait for settings to be loaded
while (!initComplete)
{
yield return null;
}
// Register settings with service locator // Register settings with service locator
ServiceLocator.Register<IPlayerFollowerSettings>( if (playerSettings != null)
SettingsProvider.Instance.GetSettings<PlayerFollowerSettings>()); {
ServiceLocator.Register<IPlayerFollowerSettings>(playerSettings);
Debug.Log("PlayerFollowerSettings registered successfully");
}
else
{
Debug.LogError("Failed to load PlayerFollowerSettings");
}
ServiceLocator.Register<IInteractionSettings>( if (interactionSettings != null)
SettingsProvider.Instance.GetSettings<InteractionSettings>()); {
ServiceLocator.Register<IInteractionSettings>(interactionSettings);
Debug.Log("InteractionSettings registered successfully");
}
else
{
Debug.LogError("Failed to load InteractionSettings");
}
ServiceLocator.Register<IMinigameSettings>( if (minigameSettings != null)
SettingsProvider.Instance.GetSettings<MinigameSettings>()); {
ServiceLocator.Register<IMinigameSettings>(minigameSettings);
Debug.Log("MinigameSettings registered successfully");
}
else
{
Debug.LogError("Failed to load MinigameSettings");
}
// Log success // Log success
Debug.Log("All settings loaded and registered with ServiceLocator"); _settingsLoaded = playerSettings != null && interactionSettings != null && minigameSettings != null;
_settingsLoaded = true; if (_settingsLoaded)
{
Debug.Log("All settings loaded and registered with ServiceLocator");
}
else
{
Debug.LogWarning("Some settings failed to load - check that all settings assets exist and are marked as Addressables");
}
// Migrate settings if needed // Migrate settings if needed
if (legacyGameSettings != null) if (legacyGameSettings != null && !playerSettings && !interactionSettings && !minigameSettings)
{ {
MigrateFromLegacySettings(); Debug.LogWarning("Legacy settings detected but failed to load new settings. Consider running the migration tool.");
} }
} }
private void MigrateFromLegacySettings()
{
// This method can be used to copy settings from the old GameSettings to the new system
// Implement if needed for your production environment
Debug.Log("Legacy settings migration available but not implemented.");
}
void OnApplicationQuit() void OnApplicationQuit()
{ {

View File

@@ -8,6 +8,7 @@ namespace AppleHills.Core.Settings
{ {
/// <summary> /// <summary>
/// Responsible for loading and caching settings from Addressables. /// Responsible for loading and caching settings from Addressables.
/// Uses synchronous loading to ensure settings are available immediately.
/// </summary> /// </summary>
public class SettingsProvider : MonoBehaviour public class SettingsProvider : MonoBehaviour
{ {
@@ -43,64 +44,57 @@ namespace AppleHills.Core.Settings
} }
/// <summary> /// <summary>
/// Load settings asynchronously using Addressables /// Load settings synchronously using Addressables - blocks until complete
/// </summary> /// </summary>
public void LoadSettings<T>(Action<T> onLoaded) where T : BaseSettings public T LoadSettingsSynchronous<T>() where T : BaseSettings
{ {
string key = typeof(T).Name; string key = typeof(T).Name;
// Return from cache if already loaded // Return from cache if already loaded
if (_settingsCache.TryGetValue(key, out BaseSettings cachedSettings)) if (_settingsCache.TryGetValue(key, out BaseSettings cachedSettings))
{ {
onLoaded?.Invoke(cachedSettings as T); return cachedSettings as T;
return;
} }
// Load using Addressables // Load using Addressables synchronously
Addressables.LoadAssetAsync<T>($"Settings/{key}.asset").Completed += handle => try
{ {
if (handle.Status == AsyncOperationStatus.Succeeded) // WaitForCompletion blocks until the asset is loaded
T settings = Addressables.LoadAssetAsync<T>($"Settings/{key}").WaitForCompletion();
if (settings != null)
{ {
_settingsCache[key] = handle.Result; _settingsCache[key] = settings;
onLoaded?.Invoke(handle.Result); return settings;
} }
else else
{ {
Debug.LogError($"Failed to load settings: {key}"); Debug.LogError($"Failed to load settings: {key}");
onLoaded?.Invoke(null);
} }
};
}
/// <summary>
/// Get cached settings
/// </summary>
public T GetSettings<T>() where T : BaseSettings
{
string key = typeof(T).Name;
if (_settingsCache.TryGetValue(key, out BaseSettings settings))
{
return settings as T;
} }
catch (Exception e)
{
Debug.LogError($"Error loading settings {key}: {e.Message}");
}
return null; return null;
} }
/// <summary> /// <summary>
/// Preload all settings - call this at game startup /// Get cached settings or load them synchronously if not cached
/// </summary> /// </summary>
public void PreloadAllSettings(Action onComplete) public T GetSettings<T>() where T : BaseSettings
{ {
// Load all necessary settings types string key = typeof(T).Name;
int pendingLoads = 3; // Number of settings types
Action decrementCounter = () => {
pendingLoads--;
if (pendingLoads <= 0)
onComplete?.Invoke();
};
LoadSettings<PlayerFollowerSettings>(settings => decrementCounter()); // Return from cache if already loaded
LoadSettings<InteractionSettings>(settings => decrementCounter()); if (_settingsCache.TryGetValue(key, out BaseSettings cachedSettings))
LoadSettings<MinigameSettings>(settings => decrementCounter()); {
return cachedSettings as T;
}
// Load synchronously if not in cache
return LoadSettingsSynchronous<T>();
} }
} }
} }