Skip to content

Settings Persistence

Taiizor edited this page Jun 5, 2026 · 2 revisions

Settings Persistence

This page explains how Sucrose persists its configuration: the JSON file format, the three SettingManager implementations, the custom serializers, the mutex-guarded cross-process I/O, and the self-healing reset behavior. It is aimed at developers and power users who want to understand or safely hand-edit the on-disk settings. For where the files live see Data-Locations; for the catalog of keys and defaults see Settings-All-Keys.

Contents

Mental model

Sucrose stores all persistent configuration as a set of small JSON files, one file per topical category (General.json, Engine.json, Portal.json, …). There is no single monolithic config file, and settings are never stored in the registry (the only registry write is to Windows' own DirectX GPU-preference key — see Data-Locations). The files live under %AppData%\Sucrose\Setting\.

The three collaborating libraries

Library Role
Sucrose.Memory Pure constant/readonly definitions — the strings: setting key names, folder names, file names, exe names, URLs, mutex names. No logic; the single source of truth for magic strings.
Sucrose.Manager The persistence engine: SettingManager reads/writes the JSON files, LogManager writes logs, and the Manage.* static classes expose strongly-typed, defaulted, clamped accessors.
Sucrose.Resources UI localization (one XAML ResourceDictionary per language) plus a resource-lookup helper. Not a settings store, but part of the shared-library set.

See Shared-Item-Projects and Code-Conventions for how these libraries fit into the overall codebase.

JSON file format

Every settings file is a single JSON object with one property, Properties, a string→object dictionary:

{
  "Properties": {
    "Culture": "EN",
    "AppVisible": true,
    "WallpaperVolume": 100
  }
}

The internal model is private class Settings { public Dictionary<string, object> Properties { get; set; } }. Notable facts:

  • The serializer is Newtonsoft.Json (JsonConvert), with Formatting.Indented by default and TypeNameHandling.None.
  • The JSON key name equals the setting constant value (e.g. the key "Culture" is the constant Constant.General.Culture).
  • Enums are written as their string member name (not the integer) — see Custom converters.

The SettingManager variants

There are three parallel implementations with the same public surface and identical JSON shape. A project picks one based on its concurrency needs:

Class File Freshness / concurrency strategy
SettingManager src/Library/Sucrose.Manager/SettingManager.cs Reloads from disk on read when File.GetLastWriteTime > _lastWrite. No lock. Provides GetSettingAddress.
SettingManager2 src/Library/Sucrose.Manager/SettingManager2.cs Same freshness check plus a lock for thread safety. No GetSettingAddress.
SettingManager3 src/Library/Sucrose.Manager/SettingManager3.cs Keeps an in-memory copy refreshed by a FileSystemWatcher (NotifyFilters.LastWrite); reads come from memory. Adds an explicit SaveSetting(); SetSetting upserts keys individually.

The central registry Sucrose.Manager.Manage.Internal instantiates one SettingManager per JSON file (one LogManager per process log).

Path composition

new SettingManager("General.json")
// → %AppData%\Sucrose\Setting\General.json
_settingsFilePath = Path.Combine(SMMRP.ApplicationData, SMMRG.AppName, SMMRF.Setting, settingsFileName);

Where Path.ApplicationData is the Roaming AppData, General.AppName is "Sucrose", and Folder.Setting is "Setting". The directory is auto-created on construction.

flowchart LR
    Acc["Manage.* accessor<br/>typed default + clamp"] --> Mgr["SettingManager<br/>freshness check / in-memory copy"]
    Mgr --> RW["mutex-guarded Reader / Writer<br/>mutex name = MD5 of file path"]
    RW --> File["%AppData%/Sucrose/Setting/<br/>&lt;Category&gt;.json"]
    File -. invalid or missing .-> Reset["auto-reset to empty Properties"]
    Reset --> File
Loading

Public API surface

All three managers expose the same methods:

Method Purpose
T GetSetting<T>(string key, T back = default) Typed read via ConvertToType<T> (handles IPAddress, Uri, Guid, enums, KeyValuePair<string,string> split on :, string[], List<string>, Dictionary<string,string>).
T GetSettingStable<T>(string key, T back = default) Read via JsonConvert.DeserializeObject<T> of the value's string (used for numeric/value types that are then clamped).
T GetSettingAddress<T>(...) (Managers 1 & 3 only) same as GetSetting, for address-like values.
void SetSetting<T>(string key, T value) / void SetSetting<T>(KeyValuePair<string,T>[] pairs) Single or batch write.
string ReadSetting() Raw file text.
void ApplySetting() Reset the file to empty { "Properties": {} }.
void SaveSetting() (Manager 3 only) persist current in-memory state.
bool CheckFile() Does the file exist.
string SettingFile() Full path to the JSON file (handy for "open settings folder" UI).

Custom converters

Two custom Newtonsoft converters are registered (in src/Library/Sucrose.Manager/Converter/):

Converter Behavior
EnumConverter Writes enums as their string name; parses case-insensitively.
IPAddressConverter Serializes an IPAddress as a plain string.

Because of EnumConverter, any enum value in a settings file is stored as text (e.g. "UniformToFill", not 5). Hand-editing must use the enum member name.

Cross-process safe I/O

Sucrose runs many processes that may touch the same file (Portal, the engines, Backgroundog, Commandog, …). I/O is therefore made safe across processes (Helper/Reader.cs, Helper/Writer.cs):

  • All reads and writes are guarded by a named Mutex whose name is an MD5-derived GUID of the file path (Helper/Unique.csGenerateText). The same file path yields the same mutex name in every process.
  • Reader.ReadStream opens with FileShare.None; Writer.WriteStream truncates-or-creates.
  • Helper/Cleaner.cs strips stray doubled braces ({{ / }}) as a defensive cleanup applied to all read/written content.
  • Helper/Validator.cs validates JSON before a file is trusted.

This is the same persistence layer used by Backup-Restore-Reset. For the broader process-to-process communication (pipes, signals, transmission) see IPC.

Self-healing and auto-reset

The constructor runs a private ControlFile() step: if a settings file is missing, empty, or invalid JSON, it is automatically reset to { "Properties": {} } via ApplySetting(). As a result, a corrupted settings file does not crash the app — it silently reverts that category to defaults on next launch. This is why "delete the file" is a valid reset for a single category (see Backup-Restore-Reset) and why corrupt-settings symptoms self-resolve (see Troubleshooting-Settings-Startup-GPU).

Additionally, GetSettingStable numeric reads are clamped in the Manage.* accessors via Skylark.Helper.Skymath.Clamp. An out-of-range value in the JSON is silently clamped at read time — but the file is not rewritten, so the raw out-of-range value remains on disk until the next write of that key.

Where keys and defaults live

Concern Lives in Example
Key name (the JSON property) Sucrose.Memory.Manage.Constant.* (const string) Constant.General.Culture"Culture"
Default value, range, clamp Sucrose.Manager.Manage.* accessor Manage.Engine.WallpaperVolume defaults to 100, clamped 0–100
File the key belongs to Sucrose.Manager.Manage.Internal GeneralSettingManagerGeneral.json

The full enumerated catalog (key, type, default, range, file) is on Settings-All-Keys; per-page detail is on Settings-Overview and its sibling pages.

Hand-editing caveats

If you edit settings JSON by hand:

  • Stop Sucrose first. Reads reload on file change, but a write from a running process can overwrite your edit (writes are mutex-guarded but not merge-aware).
  • Use enum member names, not integers (e.g. "Mica", not a number) — EnumConverter expects names.
  • Keep the { "Properties": { … } } envelope. A file without it is treated as invalid and reset on next launch.
  • Writes swallow all exceptions (catch { }): a locked or permission-denied file silently fails to persist, so verify your change survived a restart.
  • Out-of-range numeric values are clamped at read time but left on disk; re-enter a valid value if you want the file itself corrected.

See also

Home

Getting Started

Wallpaper Types

Using Sucrose

Settings Reference

Creating Wallpapers

Engine Reference

Automation & Command Line

Architecture & Internals

Data, Files & Diagnostics

Building & Contributing

Help & Support

Clone this wiki locally