Перейти к содержимому

Правила

Позиция окна −32000, кегль ноль и имя темы, которую никто не ставил, декодируются прекрасно. Когда значение не читается — про байты, которые не читаются, а rule — то место, где приложение говорит, какими должны быть те, что читаются.

Правило объявляют один раз, на поле, и оно стоит на каждом пути, которым значение проходит через это поле, в обе стороны:

  • внутрь — прочитано из store, когда структуру строят или загружают; принесено правкой файла; записано в обход поля — по пути через Store::set;
  • наружу — записано самим процессом через set, update или modify или сохранено save у загруженной структуры.

Объявленного значения по умолчанию среди них нет. Это собственное значение объявления, и его берут как написано: когда в store ничего нет, после UseDefault и как первое значение volatile-поля. Если правило такое значение поправило бы, менять надо значение по умолчанию.

Правило не отказывает. Оно берёт значение по &mut и оставляет его годным: кегль вне рамок подрезает, неустановленную тему заменяет. Что оно оставило, то и держит поле, и то же держит store: поправку на входе записывают обратно в store, а поправку при save оставляют в структуре у вас в руках. Так что никому не приходится подрезать значение перед set, чтобы оно не вышло за рамки, и ни один экран не покажет того, чего нет в файле.

Правило — голая fn: берёт значение и контекст и ничего не возвращает.

fn a_size_that_renders(size: &mut u8, _cx: &RuleContext) {
*size = (*size).clamp(6, 72);
}
fn a_theme_that_is_installed(theme: &mut String, cx: &RuleContext) {
let installed = cx.require::<InstalledThemes>();
if !installed.0.contains(&theme.as_str()) {
*theme = "dark".to_string();
}
}
#[amethystate(prefix = "ruled_ui")]
pub struct Ui {
#[amestate(default = 14u8, rule = a_size_that_renders)]
pub font_size: u8,
#[amestate(default = "dark".to_string(), rule = a_theme_that_is_installed)]
pub theme: String,
}

Контекст нужен потому, что правило — голая fn: оно ничего не захватывает. Мир, по которому она судит значение, — какие есть мониторы, какие темы установлены, — передают store при открытии.

let store = StoreBuilder::new(settings)
.context(InstalledThemes(installed))
.build()?;

По одному значению на тип. Спрашивают через cx.get::<T>() — он отвечает None, если ничего не передали, — или через cx.require::<T>(), который тогда паникует. Store, собранный без того, что нужно его правилам, собран неправильно одинаково при каждом запуске, а паника называет запрошенный тип и перечисляет, что передали вместо него.

Запись ложится такой, какой её оставило правило, — и в поле, и в store.

ui.font_size().set(200);
assert_eq!(ui.font_size().get(), 72);
assert_eq!(store.get::<u8>(["ruled_ui", "font_size"])?, Some(72));
значение правило поля
прочитано, пока строится структура выполняется; поправка записывается обратно
записано через поле: set, update, modify выполняется; запись ложится такой, какой её оставило правило
записано в обход поля: Store::set по пути выполняется, когда значение приходит; поправка записывается обратно
правка извне процесса выполняется, когда значение приходит, так же
load_with выполняется; поправка записывается обратно
save у загруженной структуры выполняется на всех полях до первой записи
шаг миграции не выполняется

Запись в обход поля поправляют уже после того, как store её принял. Поле узнаёт о ней позже store, поэтому на миг в файле лежит то, что пришло. Потом поправку записывают поверх, и в файле оказывается то же, что держит поле.

Поправка, которую store не взял, — это расхождение. Правило может превратить значение в такое, какое движок не удержит, — например, в бесконечность для движка, который пишет JSON. Тогда поле держит то, что сделало правило, store — то, что ему дали, а try_get отвечает Reason::NotWrittenBack, пока не ляжет следующее изменение.

save берёт структуру по &mut и проводит каждое поле через его правило, прежде чем что-нибудь записать. Что правило поправило, оно поправило и в структуре в руках, так что после сохранения в ней то же, что в файле.

Правило берёт и volatile-поле. Из store в него ничего не приходит, зато каждая запись идёт через правило, и оно держит в рамках значение, которое процесс хранит в памяти и никуда не кладёт.

Два поля правила не примут вовсе, и макрос скажет об этом на компиляции:

  • nested-поле. Правило поля судит одно значение по одному пути. Вложенная структура — не значение, а поддерево путей, и звать правило не с чем. Правила вешают на поля самой вложенной структуры.
  • Карта. Правило объявляют один раз и про одно значение, а записи карты — данные: они приходят и уходят на ходу, и объявленного пути, на который повесить правило, у них нет. Что карта делает с записью, которую не может прочитать, решает unreadable_entries — это на странице Поля, а не здесь.

Не примет правила и структура. Структура — место, где лежат поля, а не значение само по себе, поэтому инвариант между двумя полями — наименьшая ширина не больше наибольшей — поправляют там, где структуру открывают: Как открыть структуру по-своему.