<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Recent changes to Settings Persistence</title><link>https://sourceforge.net/p/sucrose/wiki/Settings%2520Persistence/</link><description>Recent changes to Settings Persistence</description><atom:link href="https://sourceforge.net/p/sucrose/wiki/Settings%20Persistence/feed" rel="self"/><language>en</language><lastBuildDate>Sat, 04 Jul 2026 04:15:52 -0000</lastBuildDate><atom:link href="https://sourceforge.net/p/sucrose/wiki/Settings%20Persistence/feed" rel="self" type="application/rss+xml"/><item><title>Settings Persistence modified by Bayram Emekli</title><link>https://sourceforge.net/p/sucrose/wiki/Settings%2520Persistence/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v2
+++ v3
@@ -72,7 +72,7 @@
     :::mermaid
     flowchart LR
         Acc["Manage.* accessor&lt;br/&gt;typed default + clamp"] --&amp;gt; Mgr["SettingManager&lt;br/&gt;freshness check / in-memory copy"]

-        Mgr --&amp;gt; RW["mutex-guarded Reader / Writer&lt;br/&gt;mutex name = MD5 of file path"]
+        Mgr --&amp;gt; RW["mutex-guarded Reader / Writer&lt;br/&gt;mutex name = SHA256 of file path"]
         RW --&amp;gt; File["%AppData%/Sucrose/Setting/&lt;br/&gt;&amp;lt;Category&amp;gt;.json"]
         File -. invalid or missing .-&amp;gt; Reset["auto-reset to empty Properties"]
         Reset --&amp;gt; File
@@ -108,7 +108,7 @@

 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.cs` → `GenerateText`). The same file path yields the same mutex name in every process.
+- All reads and writes are guarded by a **named `Mutex`** whose name is an SHA256-derived GUID of the file path (`Helper/Unique.cs` → `GenerateText`). 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.
&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Bayram Emekli</dc:creator><pubDate>Sat, 04 Jul 2026 04:15:52 -0000</pubDate><guid>https://sourceforge.netf5ba602b7c1199c84133d4a4ffe7697dc542d82d</guid></item><item><title>Settings Persistence modified by Bayram Emekli</title><link>https://sourceforge.net/p/sucrose/wiki/Settings%2520Persistence/</link><description>&lt;div class="markdown_content"&gt;&lt;pre&gt;--- v1
+++ v2
@@ -69,7 +69,13 @@

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

-&amp;gt; 🖼️ **Diagram needed:** Settings read/write flow — `Manage.* accessor` → `SettingManager` (freshness check / in-memory copy) → mutex-guarded `Reader`/`Writer` → `%AppData%\Sucrose\Setting\&amp;lt;category&amp;gt;.json`.

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

 ## Public API surface

&amp;lt;/category&amp;gt;&lt;/pre&gt;
&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Bayram Emekli</dc:creator><pubDate>Sat, 04 Jul 2026 04:15:43 -0000</pubDate><guid>https://sourceforge.net0c5418e9b15d8a74eed1ccb99b98d94e253c9231</guid></item><item><title>Settings Persistence modified by Bayram Emekli</title><link>https://sourceforge.net/p/sucrose/wiki/Settings%2520Persistence/</link><description>&lt;div class="markdown_content"&gt;&lt;h1 id="h-settings-persistence"&gt;Settings Persistence&lt;/h1&gt;
&lt;p&gt;This page explains &lt;em&gt;how&lt;/em&gt; Sucrose persists its configuration: the JSON file format, the three &lt;code&gt;SettingManager&lt;/code&gt; 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 &lt;em&gt;where&lt;/em&gt; the files live see &lt;a href="./Data-Locations"&gt;Data-Locations&lt;/a&gt;; for the catalog of keys and defaults see &lt;a href="./Settings-All-Keys"&gt;Settings-All-Keys&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="h-contents"&gt;Contents&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#mental-model"&gt;Mental model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-three-collaborating-libraries"&gt;The three collaborating libraries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#json-file-format"&gt;JSON file format&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-settingmanager-variants"&gt;The SettingManager variants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#public-api-surface"&gt;Public API surface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#custom-converters"&gt;Custom converters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#cross-process-safe-io"&gt;Cross-process safe I/O&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#self-healing-and-auto-reset"&gt;Self-healing and auto-reset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#where-keys-and-defaults-live"&gt;Where keys and defaults live&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#hand-editing-caveats"&gt;Hand-editing caveats&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="h-mental-model"&gt;Mental model&lt;/h2&gt;
&lt;p&gt;Sucrose stores &lt;strong&gt;all persistent configuration as a set of small JSON files&lt;/strong&gt;, one file per topical category (&lt;code&gt;General.json&lt;/code&gt;, &lt;code&gt;Engine.json&lt;/code&gt;, &lt;code&gt;Portal.json&lt;/code&gt;, …). 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 &lt;a href="./Data-Locations"&gt;Data-Locations&lt;/a&gt;). The files live under &lt;code&gt;%AppData%\Sucrose\Setting\&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="h-the-three-collaborating-libraries"&gt;The three collaborating libraries&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sucrose.Memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pure constant/readonly definitions — the &lt;em&gt;strings&lt;/em&gt;: setting key names, folder names, file names, exe names, URLs, mutex names. No logic; the single source of truth for magic strings.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sucrose.Manager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The persistence engine: &lt;code&gt;SettingManager&lt;/code&gt; reads/writes the JSON files, &lt;code&gt;LogManager&lt;/code&gt; writes logs, and the &lt;code&gt;Manage.*&lt;/code&gt; static classes expose strongly-typed, defaulted, clamped accessors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sucrose.Resources&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UI localization (one XAML &lt;code&gt;ResourceDictionary&lt;/code&gt; per language) plus a resource-lookup helper. Not a settings store, but part of the shared-library set.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;See &lt;a href="./Shared-Item-Projects"&gt;Shared-Item-Projects&lt;/a&gt; and &lt;a href="./Code-Conventions"&gt;Code-Conventions&lt;/a&gt; for how these libraries fit into the overall codebase.&lt;/p&gt;
&lt;h2 id="h-json-file-format"&gt;JSON file format&lt;/h2&gt;
&lt;p&gt;Every settings file is a single JSON object with one property, &lt;code&gt;Properties&lt;/code&gt;, a string→object dictionary:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;"Properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"Culture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"AppVisible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;"WallpaperVolume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The internal model is &lt;code&gt;private class Settings { public Dictionary&amp;lt;string, object&amp;gt; Properties { get; set; } }&lt;/code&gt;. Notable facts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The serializer is &lt;strong&gt;Newtonsoft.Json&lt;/strong&gt; (&lt;code&gt;JsonConvert&lt;/code&gt;), with &lt;code&gt;Formatting.Indented&lt;/code&gt; by default and &lt;code&gt;TypeNameHandling.None&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The JSON key name &lt;strong&gt;equals&lt;/strong&gt; the setting constant value (e.g. the key &lt;code&gt;"Culture"&lt;/code&gt; is the constant &lt;code&gt;Constant.General.Culture&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Enums are written as their &lt;strong&gt;string member name&lt;/strong&gt; (not the integer) — see &lt;a href="#custom-converters"&gt;Custom converters&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="h-the-settingmanager-variants"&gt;The SettingManager variants&lt;/h2&gt;
&lt;p&gt;There are &lt;strong&gt;three&lt;/strong&gt; parallel implementations with the same public surface and identical JSON shape. A project picks one based on its concurrency needs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Freshness / concurrency strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SettingManager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/Library/Sucrose.Manager/SettingManager.cs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reloads from disk on read when &lt;code&gt;File.GetLastWriteTime &amp;gt; _lastWrite&lt;/code&gt;. No lock. Provides &lt;code&gt;GetSettingAddress&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SettingManager2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/Library/Sucrose.Manager/SettingManager2.cs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same freshness check &lt;strong&gt;plus a &lt;code&gt;lock&lt;/code&gt;&lt;/strong&gt; for thread safety. No &lt;code&gt;GetSettingAddress&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SettingManager3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/Library/Sucrose.Manager/SettingManager3.cs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keeps an in-memory copy refreshed by a &lt;strong&gt;&lt;code&gt;FileSystemWatcher&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;NotifyFilters.LastWrite&lt;/code&gt;); reads come from memory. Adds an explicit &lt;code&gt;SaveSetting()&lt;/code&gt;; &lt;code&gt;SetSetting&lt;/code&gt; upserts keys individually.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The central registry &lt;code&gt;Sucrose.Manager.Manage.Internal&lt;/code&gt; instantiates one &lt;code&gt;SettingManager&lt;/code&gt; per JSON file (one &lt;code&gt;LogManager&lt;/code&gt; per process log).&lt;/p&gt;
&lt;h3 id="h-path-composition"&gt;Path composition&lt;/h3&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;SettingManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"General.json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → %AppData%\Sucrose\Setting\General.json&lt;/span&gt;
&lt;span class="n"&gt;_settingsFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SMMRP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplicationData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SMMRG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SMMRF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Setting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settingsFileName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;code&gt;Path.ApplicationData&lt;/code&gt; is the Roaming AppData, &lt;code&gt;General.AppName&lt;/code&gt; is &lt;code&gt;"Sucrose"&lt;/code&gt;, and &lt;code&gt;Folder.Setting&lt;/code&gt; is &lt;code&gt;"Setting"&lt;/code&gt;. The directory is auto-created on construction.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🖼️ &lt;strong&gt;Diagram needed:&lt;/strong&gt; Settings read/write flow — &lt;code&gt;Manage.* accessor&lt;/code&gt; → &lt;code&gt;SettingManager&lt;/code&gt; (freshness check / in-memory copy) → mutex-guarded &lt;code&gt;Reader&lt;/code&gt;/&lt;code&gt;Writer&lt;/code&gt; → &lt;code&gt;%AppData%\Sucrose\Setting\&amp;lt;Category&amp;gt;.json&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="h-public-api-surface"&gt;Public API surface&lt;/h2&gt;
&lt;p&gt;All three managers expose the same methods:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;T GetSetting&amp;lt;T&amp;gt;(string key, T back = default)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Typed read via &lt;code&gt;ConvertToType&amp;lt;T&amp;gt;&lt;/code&gt; (handles &lt;code&gt;IPAddress&lt;/code&gt;, &lt;code&gt;Uri&lt;/code&gt;, &lt;code&gt;Guid&lt;/code&gt;, enums, &lt;code&gt;KeyValuePair&amp;lt;string,string&amp;gt;&lt;/code&gt; split on &lt;code&gt;:&lt;/code&gt;, &lt;code&gt;string[]&lt;/code&gt;, &lt;code&gt;List&amp;lt;string&amp;gt;&lt;/code&gt;, &lt;code&gt;Dictionary&amp;lt;string,string&amp;gt;&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;T GetSettingStable&amp;lt;T&amp;gt;(string key, T back = default)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read via &lt;code&gt;JsonConvert.DeserializeObject&amp;lt;T&amp;gt;&lt;/code&gt; of the value's string (used for numeric/value types that are then clamped).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;T GetSettingAddress&amp;lt;T&amp;gt;(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Managers 1 &amp;amp; 3 only) same as &lt;code&gt;GetSetting&lt;/code&gt;, for address-like values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;void SetSetting&amp;lt;T&amp;gt;(string key, T value)&lt;/code&gt; / &lt;code&gt;void SetSetting&amp;lt;T&amp;gt;(KeyValuePair&amp;lt;string,T&amp;gt;[] pairs)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Single or batch write.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;string ReadSetting()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raw file text.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;void ApplySetting()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reset the file to empty &lt;code&gt;{ "Properties": {} }&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;void SaveSetting()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(Manager 3 only) persist current in-memory state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bool CheckFile()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Does the file exist.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;string SettingFile()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full path to the JSON file (handy for "open settings folder" UI).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="h-custom-converters"&gt;Custom converters&lt;/h2&gt;
&lt;p&gt;Two custom Newtonsoft converters are registered (in &lt;code&gt;src/Library/Sucrose.Manager/Converter/&lt;/code&gt;):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Converter&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EnumConverter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes enums as their &lt;strong&gt;string name&lt;/strong&gt;; parses case-insensitively.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IPAddressConverter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Serializes an &lt;code&gt;IPAddress&lt;/code&gt; as a plain string.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Because of &lt;code&gt;EnumConverter&lt;/code&gt;, any enum value in a settings file is stored as text (e.g. &lt;code&gt;"UniformToFill"&lt;/code&gt;, not &lt;code&gt;5&lt;/code&gt;). Hand-editing must use the enum member name.&lt;/p&gt;
&lt;h2 id="h-cross-process-safe-io"&gt;Cross-process safe I/O&lt;/h2&gt;
&lt;p&gt;Sucrose runs many processes that may touch the same file (Portal, the engines, Backgroundog, Commandog, …). I/O is therefore made safe across processes (&lt;code&gt;Helper/Reader.cs&lt;/code&gt;, &lt;code&gt;Helper/Writer.cs&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All reads and writes are guarded by a &lt;strong&gt;named &lt;code&gt;Mutex&lt;/code&gt;&lt;/strong&gt; whose name is an MD5-derived GUID of the file path (&lt;code&gt;Helper/Unique.cs&lt;/code&gt; → &lt;code&gt;GenerateText&lt;/code&gt;). The same file path yields the same mutex name in every process.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Reader.ReadStream&lt;/code&gt; opens with &lt;code&gt;FileShare.None&lt;/code&gt;; &lt;code&gt;Writer.WriteStream&lt;/code&gt; truncates-or-creates.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Helper/Cleaner.cs&lt;/code&gt; strips stray doubled braces (&lt;code&gt;{{&lt;/code&gt; / &lt;code&gt;}}&lt;/code&gt;) as a defensive cleanup applied to all read/written content.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Helper/Validator.cs&lt;/code&gt; validates JSON before a file is trusted.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the same persistence layer used by &lt;a href="./Backup-Restore-Reset"&gt;Backup-Restore-Reset&lt;/a&gt;. For the broader process-to-process communication (pipes, signals, transmission) see &lt;a href="/p/sucrose/wiki/IPC/"&gt;IPC&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="h-self-healing-and-auto-reset"&gt;Self-healing and auto-reset&lt;/h2&gt;
&lt;p&gt;The constructor runs a private &lt;code&gt;ControlFile()&lt;/code&gt; step: &lt;strong&gt;if a settings file is missing, empty, or invalid JSON, it is automatically reset&lt;/strong&gt; to &lt;code&gt;{ "Properties": {} }&lt;/code&gt; via &lt;code&gt;ApplySetting()&lt;/code&gt;. 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 &lt;a href="./Backup-Restore-Reset"&gt;Backup-Restore-Reset&lt;/a&gt;) and why corrupt-settings symptoms self-resolve (see &lt;a href="./Troubleshooting-Settings-Startup-GPU"&gt;Troubleshooting-Settings-Startup-GPU&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Additionally, &lt;code&gt;GetSettingStable&lt;/code&gt; numeric reads are clamped in the &lt;code&gt;Manage.*&lt;/code&gt; accessors via &lt;code&gt;Skylark.Helper.Skymath.Clamp&lt;/code&gt;. An out-of-range value in the JSON is silently clamped at read time — but &lt;strong&gt;the file is not rewritten&lt;/strong&gt;, so the raw out-of-range value remains on disk until the next write of that key.&lt;/p&gt;
&lt;h2 id="h-where-keys-and-defaults-live"&gt;Where keys and defaults live&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Lives in&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Key name (the JSON property)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sucrose.Memory.Manage.Constant.*&lt;/code&gt; (&lt;code&gt;const string&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Constant.General.Culture&lt;/code&gt; → &lt;code&gt;"Culture"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default value, range, clamp&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sucrose.Manager.Manage.*&lt;/code&gt; accessor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Manage.Engine.WallpaperVolume&lt;/code&gt; defaults to &lt;code&gt;100&lt;/code&gt;, clamped 0–100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File the key belongs to&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Sucrose.Manager.Manage.Internal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GeneralSettingManager&lt;/code&gt; → &lt;code&gt;General.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The full enumerated catalog (key, type, default, range, file) is on &lt;a href="./Settings-All-Keys"&gt;Settings-All-Keys&lt;/a&gt;; per-page detail is on &lt;a href="./Settings-Overview"&gt;Settings-Overview&lt;/a&gt; and its sibling pages.&lt;/p&gt;
&lt;h2 id="h-hand-editing-caveats"&gt;Hand-editing caveats&lt;/h2&gt;
&lt;p&gt;If you edit settings JSON by hand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stop Sucrose first.&lt;/strong&gt; Reads reload on file change, but a write from a running process can overwrite your edit (writes are mutex-guarded but not merge-aware).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use enum member names&lt;/strong&gt;, not integers (e.g. &lt;code&gt;"Mica"&lt;/code&gt;, not a number) — &lt;code&gt;EnumConverter&lt;/code&gt; expects names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the &lt;code&gt;{ "Properties": { … } }&lt;/code&gt; envelope.&lt;/strong&gt; A file without it is treated as invalid and reset on next launch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writes swallow all exceptions&lt;/strong&gt; (&lt;code&gt;catch { }&lt;/code&gt;): a locked or permission-denied file silently fails to persist, so verify your change survived a restart.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="h-see-also"&gt;See also&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="./Data-Locations"&gt;Data-Locations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./Settings-All-Keys"&gt;Settings-All-Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./Settings-Overview"&gt;Settings-Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./Backup-Restore-Reset"&gt;Backup-Restore-Reset&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./Troubleshooting-Settings-Startup-GPU"&gt;Troubleshooting-Settings-Startup-GPU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./Shared-Item-Projects"&gt;Shared-Item-Projects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Bayram Emekli</dc:creator><pubDate>Sat, 04 Jul 2026 04:15:36 -0000</pubDate><guid>https://sourceforge.netdcdbd2a10fee44ffed032f566dd721b5a4ea9b00</guid></item></channel></rss>