Машина состояний — один из самых интуитивно понятных подходов. Нарисовал квадратики, провёл стрелочки, описал условия переходов — готово.
И если для анимаций, поведения противников, состояний предметов и квестов — использование оправдано и обосновано.
То когда состояния начинают просачиваться в глобальное управление — подключениями, сценами, окнами UI — появляется проблема.
Ты не сможешь продумать все возможные события и условия переходов. Рано или поздно это приведёт к зависанию в одном состоянии машины.
Дийкстра рассуждал об этом ещё в 68 году — описывая:
В мультипрограммных системах, где несколько процессов работают параллельно с неопределённым порядком прерываний, возникают часто ошибки невоспроизводимые — зависящие от случайного момента прерывания.
Чуть более простым языком эту проблему можно описать так:
Событие, поступившее конечному автомату в некотором контексте, может не вызвать никакого действия, поскольку автомат не в состоянии однозначно определить, какое именно действие должно быть выполнено, исходя из полученного входного сигнала и текущего состояния машины.🔸Пример 1: Loading
Сцена Loading — инициализация, подключение к серверу, скачивание конфигов.
Следующее состояние — Meta, главное меню.
Переход Loading → Meta срабатывает по условию: «все конфиги загружены и соединение активно».
Пользователь во время скачивания свернул приложение чтобы прочитать новый пост по архитектуре 😎 и вернулся через 10 минут. Соединение отвалилось по таймауту, часть конфигов скачалась, часть — нет.
Приложение получает OnApplicationPause(false) — «пользователь вернулся». Машина всё ещё в Loading. Но конфиги не все, а соединение мертво.
Перехода «переподключиться» не предусмотрено, потому что автор не заложил комбинацию «Loading + потеря соединения + частичная загрузка + выход из паузы».
Событие пришло, а ни один переход не сработал — игра зависла в Loading навсегда.
🔹Пример 2: Пауза
Цепочка уровней и игровой цикл: открыл уровень → бой → противники убиты → сбор наград → заход в триггер следующего уровня → повторить. Очень легко описать в виде машины.
Помимо этих состояний есть ещё Paused — оно активируется кнопкой окна настроек.
На стадии «заход в триггер следующего уровня» пользователь делает frame-perfext noscope360 в пиксель кнопки настроек и открывает окно, когда не должен 🤣
Машина переходит в Paused, но перехода из Paused обратно в «триггер следующего уровня» не существует — он просто не был прописан.
Окно закрывается, а игра остаётся в Paused навсегда.
🔸И вот ключевой момент
Казалось бы — пойди да пропиши переход, делов на 5 минут.
Но сделав это, где гарантия что в такую же ситуацию не попадёт любое другое состояние машины?
А их нет! Нужно в каждом месте, которое полагается на машину, прописывать обработку всех возможных состояний.
И это большая проблема. Если система так написана, сроки фикса жмут, времени всё переписывать нет — приходится костылить: запрещать или обрабатывать конкретный переход.
🔻Каждое новое состояние или событие мультиплицирует количество переходов, которые нужно определить. N состояний и M событий — это N×M переходов, и если хоть один не определён, есть риск перехода в невалидное состояние.
У приложения и UI события приходят в неопределённом порядке, по кругу, без финальной точки — ровно та ситуация, которую Дийкстра описал как источник невоспроизводимых ошибок.
И могу лишь подтвердить из своего опыта. Такие ошибки всегда самые сложные для исправления т.к. очень сложно всегда найти репро.
🔻Так что пожалуйста, не надо. Вам не нужна машина состояний для "контроля" состояния приложения и окон 🫠
Вредно как со стороны сложности проекта, так и со стороны нашего ментального здоровья.
Развивает параною "а я точно исправил проблему или опять какой-то кейс не покрыл" 😵💫
Делитесь своими историями исправления сложных багов в комментах, уверен у каждого есть пачка таких 🫡
Ставь 👍 если тебе заходит такого рода контент!
Ты знаешь кому переслать эту статью 💪
#проект_в_разработке@UniArchitect
#проект_с_нуля@UniArchitect
