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

Field<T>

Поле, объявленное через #[amethystate], — это хендл на один путь в store. Читают из памяти, пишут тоже сразу в память, а на диск — по дебаунсу.

let port = state.port().get();
state.port().set(9090);
let raised = state.port().update(|port| port + 1);
state.port().modify(|port| *port += 1);

get отдаёт само значение. У поля оно есть всегда: если по пути ничего не лежит, get вернёт то, что стоит в default у этого поля.

fn get(&self) -> T
fn try_get(&self) -> Result<T, Disagreement>
fn set(&self, value: T)
fn update<F: FnOnce(T) -> T>(&self, f: F) -> T
fn modify<F: FnOnce(&mut T)>(&self, f: F)

Запись ничего не отвечает: с тем, что может её остановить, вызывающему делать нечего, — см. чего запись не возьмёт. И Err у try_get — тоже не провал самого вопроса, а то, о чём поле и store не договорились.

get — чтение терпимое: значение оно отдаёт всегда и падать ему нечем. try_get — то же чтение, но оно ещё и скажет, если store с ответом не согласен.

Err приходит, когда пришедшее изменение не декодируется в тип поля — файл правили мимо процесса, миграция что-то за собой оставила, кодек принял значение, которое сам же прочитать не может, — или когда объявленное правило поправило сохранённое значение, а store поправку не взял.

Поле в это время держит последнее значение, с которым store был согласен, а подписчиков никто не зовёт. То, что оно отдаёт, было правдой мгновение назад; против него default — всего лишь догадка, сделанная на компиляции, и из всего, чем это значение могло оказаться, она наименее вероятная. Поэтому get отвечает дальше, а try_get — то место, где узнаёт тот, кому не всё равно.

Само обращение не падает никогда: упало раньше, а try_get только про это рассказывает. И снова отвечает Ok, как только очередное изменение декодируется, — то есть держится ровно столько, сколько остаётся правдой.

Значение, которое не читалось уже когда структуру открывали, — другой момент, и решает его объявление: Когда значение не читается.

update и modify — это прочитать, поменять, записать, и они не атомарны. Две такие операции, столкнувшись на одном поле, потеряют один из двух результатов — ровно как две пары get-потом-set. Атомарными их в store ничто не делает. Где это важно, дайте полю одного писателя: один поток или свой замок вокруг чтения и записи — Конкурентный доступ.

update возвращает посчитанное значение — это избавляет от get следующей строкой. Если у поля есть правило, легло то, что от значения оставило правило, и прочитать это можно через get.

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

Как вместо этого дождаться диска: Durability. Ожидание может довести до диска не только это поле, а насколько больше — решает движок; там же и сказано, какой как.

Объявленное правило поля ничего не отклоняет: оно поправляет значение, и запись ложится такой, какой её оставило правило.

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

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

Где запись обязана ответить — значение ввёл человек, движок выбрали в рантайме, — есть durable(): эта запись отвечает Result.

  • Как подписаться на изменения — в своём потоке или в чужом: Подписки.
  • Коллекции, чьи ключи появляются в рантайме: ReactiveMap.