Add a first pass at documentation for the settings framework

This commit is contained in:
Jon Evans 2020-10-06 21:09:03 -04:00
parent 56e1afb12e
commit b31ebae651
1 changed files with 134 additions and 0 deletions

View File

@ -0,0 +1,134 @@
# Settings Framework
The settings framework manages application settings, as well as projects. This document explains
how to make use of the framework as well as some of its inner workings.
[TOC]
## Source Code Guide
Most of the relevant code is in `common/settings` and `common/project`.
C++ Class | Description
:------------------------|:-------------
`SETTINGS_MANAGER` | Loads and unloads settings files and projects
`JSON_SETTINGS` | The base class for all settings objects. Represents a single JSON file.
`NESTED_SETTINGS` | A `JSON_SETTINGS` object stored within another (i.e. without its own file)
`PARAM` | A parameter: helper class for storing data inside a `JSON_SETTINGS`
`APP_SETTINGS_BASE` | Base class for application (frame) settings
`COLOR_SETTINGS` | A subclass of `JSON_SETTINGS` designed for storing color themes
`COMMON_SETTINGS` | The settings available to every part of KiCad
`PROJECT_FILE` | A `JSON_SETTINGS` representing a project (`.kicad_pro`) file
`PROJECT_LOCAL_SETTINGS` | A `JSON_SETTINGS` representing a project local state (`.kicad_prl`) file
## Where Settings are Stored
There are four main places a setting might be stored:
1) In `COMMON_SETTINGS`: this is a setting that is shared between all parts of KiCad.
2) In an application settings object (subclass of `APP_SETTINGS_BASE`). These objects, such as
`EESCHEMA_SETTINGS` and `PCBNEW_SETTINGS`, store settings that are specific to a portion of
KiCad. In particular, these objects are compiled inside the context of their respective
application, so they have access to data types that may not be part of `common`.
3) In the `PROJECT_FILE`, where they will be specific to a loaded project. This is true of most of
the settings found in the Board / Schematic Setup dialogs. Currently, KiCad only supports having
one project loaded at a time, and a number of places in the code expect that a `PROJECT` object
will always be available. Because of this, the `SETTINGS_MANAGER` will always ensure that a
"dummy" `PROJECT_FILE` is available even when no project has been loaded by the user. This dummy
project can be modified in memory but not saved to disk.
4) In the `PROJECT_LOCAL_SETTINGS` object, where they will be specific to a loaded project. This
file is for settings that are "local state", such as which board layers are visible, that should
(for many users, at least) not be checked in to source control. Any setting here should be
transient, meaning there will be no ill effect if the entire file is deleted.
## JSON_SETTINGS
The `JSON_SETTINGS` class is the backbone of the settings infrastructure. It is a subclass of the
`nlohmann::json::basic_json` class provided by `thirdparty/nlohmann_json/nlohmann/json.hpp`. As
such, anytime raw manipulation of the underlying JSON data is needed, you can use the [standard
`nlohmann::json` API](https://nlohmann.github.io/json/api/basic_json/). The JSON contents represent
the **state of the file on disk**, not the state of the data exposed to C++. Synchronization
between the two is done via parameters (see below) and takes place right after loading from disk and
right before saving to disk.
## Parameters
Parameters establish the link between C++ data and content in the JSON file. In general, parameters
consist of a **path**, **pointer**, and **default value**. The path is a string of the form
`"x.y.z"`, where each component represents a nested JSON dictionary key. The pointer is a pointer
to the C++ member variable that holds the data accessible to consumers of the `JSON_SETTINGS`. The
default value is used to update the pointer when the data is missing from the JSON file.
Parameters are subclasses of `PARAM_BASE` in `include/settings/parameters.h`. There are a number of
helpful subclasses created to make it easier to store complex data in JSON files.
The basic `PARAM` class is templated and is useful for storing any data type can be serialized to
JSON automatically. A basic instantiation of a `PARAM` might look like:
m_params.emplace_back( new PARAM<int>( "appearance.icon_scale",
&m_Appearance.icon_scale, 0 ) );
Here, `m_Appearance.icon_scale` is a public member of the settings object (an `int` inside a
`struct`). `"appearance.icon_scale"` is the **path** to store the value in the JSON file, and `0`
is the default value. This would result in JSON looking like this:
{
"appearance": {
"icon_scale": 0
}
}
Note that it is possible to use custom types with `PARAM<>` as long as they have a `to_json` and
`from_json` defined. See `COLOR4D` for an example of this.
For storing complex data types, it is sometimes easiest to use `PARAM_LAMBDA<>`, which allows you
to define a "getter" and "setter" as part of the parameter definition. You can use this to build
a `nlohmann::json` object and store it as the "value" of your parameter. For examples of how this
is done, see `NET_SETTINGS`.
## NESTED_SETTINGS
The `NESTED_SETTINGS` class is like a `JSON_SETTINGS` but instead of a file for a backing store, it
uses another `JSON_SETTINGS` object. The entire contents of a `NESTED_SETTINGS` are stored as the
value of a particular key in the parent file. This has two key benefits:
1) You can split up large sets of settings in to more manageable pieces
2) You can hide knowledge about the nested settings from the parent settings object
For example, many portions of the project file are stored as `NESTED_SETTINGS` objects inside the
`PROJECT_FILE`. These objects, including `SCHEMATIC_SETTINGS`, `NET_SETTINGS`, and
`BOARD_DESIGN_SETTINGS`, are compiled as part of eeschema or pcbnew, so they have access to data
types not available in common (where `PROJECT_FILE` is compiled).
When the outer file is loaded, all of the data for the nested settings is there in the underlying
`nlohmann::json` data store -- it's just not used until the appropriate `NESTED_SETTINGS` is loaded.
`NESTED_SETTINGS` objects can have shorter lifecycles than the parent. This is required because in
some cases (such as with the project file), the parent can stay resident in one frame (the KiCad
manager, for example) while a frame that uses a nested settings inside it can be created and
destroyed. When the nested settings is destroyed, it ensures that its data is stored to the JSON
data of the parent. The parent `JSON_SETTINGS` can then be saved to disk, if desired.
## Schema Version and Migrations
Settings objects have a **schema version**, which is a const integer that can be incremented when a
migration is needed. The schema version in the code is compared to that in the loaded file, and if
the file version is lower (older), **migrations** are run to bring the data in the file up to date.
**Migrations** are functions that are responsible for making the necessary changes from one schema
version to another. They act on the **underlying JSON data**, before parameter loading has taken
place.
Migrations are not always needed when changing a settings file. You are free to add or remove
parameters without changing the schema version or writing migrations. If you add parameters, they
will be added to the JSON file and initialized to their default value. If you remove parameters,
they will be silently dropped from the JSON file the next time the settings are saved. Migration is
only needed when you need to make changes to the JSON file that depend on the current state. For
example, if you decide to rename a settings key, but want to preserve the user's present setting.
If you need to make a "breaking change" to a settings file:
1) Increment the schema version
2) Write a migration that makes the necessary changes to the underlying `nlohmann::json` object
3) Call `JSON_SETTINGS::registerMigration` in the constructor for the object