using System; using System.Collections.Generic; using UnityEngine; namespace DanroJump.Settings { /// /// Runtime-сервис операторских настроек: хранит значения, валидирует их по базе и сохраняет во внешний файл. /// public sealed class ServiceSettingsService : IServiceSettingsService { private readonly ServiceSettingsDatabase database; private readonly IServiceSettingsRepository repository; private readonly IServiceSettingApplier applier; private readonly Dictionary values = new(StringComparer.Ordinal); /// /// Создает сервис поверх базы описаний, репозитория сохранения и опционального applier. /// public ServiceSettingsService( ServiceSettingsDatabase database, IServiceSettingsRepository repository, IServiceSettingApplier applier = null) { this.database = database; this.repository = repository; this.applier = applier ?? new NullServiceSettingApplier(); } /// /// База описаний настроек, используемая сервисом. /// public ServiceSettingsDatabase Database => database; public event Action SettingsLoaded; public event Action SettingsSaved; public event Action SettingChanged; /// /// Загружает сохраненные значения, дополняет дефолтами и применяет их. /// public void Load() { values.Clear(); if (repository != null && repository.TryLoad(out var loadedValues)) { foreach (var pair in loadedValues) { if (TryGetDefinition(pair.Key, out var definition) && IsStoredValueDefinition(definition)) { values[pair.Key] = definition.Sanitize(pair.Value); } } } EnsureDefaults(); ApplyAll(); SettingsLoaded?.Invoke(); } /// /// Сохраняет текущий snapshot настроек в репозиторий. /// public void Save() { if (repository == null || !repository.Save(values)) { return; } SettingsSaved?.Invoke(); } /// /// Перечитывает только значения, разрешенные для внешних изменений. /// public void ReloadExternalChanges() { if (repository == null || !repository.TryLoad(out var loadedValues)) { return; } foreach (var pair in loadedValues) { SetExternal(pair.Key, pair.Value); } } /// /// Повторно применяет все значения через applier. /// public void ApplyAll() { if (database == null) { return; } foreach (var definition in database.Entries) { if (!IsStoredValueDefinition(definition)) { continue; } applier.Apply(definition, Get(definition.Key)); } } /// /// Сбрасывает все настройки к дефолтам из базы и сохраняет результат. /// public void ResetAllToDefaults() { if (database == null) { return; } foreach (var definition in database.Entries) { if (!IsStoredValueDefinition(definition)) { continue; } Set(definition.Key, definition.DefaultValue); } Save(); } /// /// Сбрасывает одну группу настроек к дефолтам. /// public void ResetGroupToDefaults(string groupName) { if (database == null || string.IsNullOrWhiteSpace(groupName)) { return; } foreach (var definition in database.Entries) { if (!IsStoredValueDefinition(definition) || !string.Equals(definition.Group, groupName, StringComparison.OrdinalIgnoreCase)) { continue; } Set(definition.Key, definition.DefaultValue); } Save(); } /// /// Устанавливает значение настройки после проверки и sanitization. /// public bool Set(string key, ServiceSettingValue value) { if (!TryGetDefinition(key, out var definition)) { Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Unknown setting key '{key}'."); return false; } if (definition.ValueType == ServiceSettingValueType.Button) { Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Button setting '{key}' does not store a value."); return false; } var sanitized = definition.Sanitize(value); if (values.TryGetValue(key, out var current) && current.Equals(sanitized)) { return true; } // Значение применяем сразу, а Save отвечает только за запись на диск. values[key] = sanitized; applier.Apply(definition, sanitized); SettingChanged?.Invoke(key, sanitized); return true; } /// /// Устанавливает значение из внешнего файла только для разрешенных настроек. /// public bool SetExternal(string key, ServiceSettingValue value) { if (!TryGetDefinition(key, out var definition)) { Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Unknown external setting key '{key}'."); return false; } if (!definition.ExposedForExternalChanges) { Debug.LogWarning($"[{nameof(ServiceSettingsService)}] Setting '{key}' is not exposed for external changes."); return false; } return Set(key, value); } /// /// Устанавливает bool-настройку по ключу. /// public bool SetBool(string key, bool value) { return Set(key, ServiceSettingValue.Bool(value)); } /// /// Устанавливает int-настройку по ключу. /// public bool SetInt(string key, int value) { return Set(key, ServiceSettingValue.Int(value)); } /// /// Устанавливает float-настройку по ключу. /// public bool SetFloat(string key, float value) { return Set(key, ServiceSettingValue.Float(value)); } /// /// Устанавливает string-настройку по ключу. /// public bool SetString(string key, string value) { return Set(key, ServiceSettingValue.String(value)); } /// /// Устанавливает option-настройку по индексу варианта. /// public bool SetOptionIndex(string key, int optionIndex) { return Set(key, ServiceSettingValue.Option(optionIndex)); } /// /// Пытается найти описание настройки по ключу. /// public bool TryGetDefinition(string key, out ServiceSettingDefinition definition) { definition = null; return !string.IsNullOrWhiteSpace(key) && database != null && database.TryFind(key, out definition); } /// /// Возвращает копию текущих значений для безопасного чтения снаружи. /// public IReadOnlyDictionary Snapshot() { return new Dictionary(values, StringComparer.Ordinal); } /// /// Возвращает текущее значение или дефолт из базы. /// public ServiceSettingValue Get(string key) { if (values.TryGetValue(key, out var value)) { return value; } return TryGetDefinition(key, out var definition) ? definition.DefaultValue : default; } /// /// Возвращает bool-значение настройки. /// public bool GetBool(string key) { return Get(key).boolValue; } /// /// Возвращает int-значение настройки. /// public int GetInt(string key) { return Get(key).intValue; } /// /// Возвращает float-значение настройки. /// public float GetFloat(string key) { return Get(key).floatValue; } /// /// Возвращает string-значение настройки. /// public string GetString(string key) { return Get(key).stringValue ?? string.Empty; } /// /// Возвращает индекс выбранного option-варианта. /// public int GetOptionIndex(string key) { return Get(key).intValue; } /// /// Заполняет отсутствующие значения дефолтами и заново sanitizes загруженные данные. /// private void EnsureDefaults() { if (database == null) { return; } foreach (var definition in database.Entries) { if (!IsStoredValueDefinition(definition)) { continue; } values[definition.Key] = values.TryGetValue(definition.Key, out var existingValue) ? definition.Sanitize(existingValue) : definition.Sanitize(definition.DefaultValue); } } /// /// Проверяет, что описание настройки пригодно для работы сервиса. /// private static bool IsValidDefinition(ServiceSettingDefinition definition) { return definition != null && !string.IsNullOrWhiteSpace(definition.Key); } /// /// Проверяет, что настройка хранит значение. Кнопки являются действиями и не попадают в JSON. /// private static bool IsStoredValueDefinition(ServiceSettingDefinition definition) { return IsValidDefinition(definition) && definition.ValueType != ServiceSettingValueType.Button; } } }