Обновить

Конечный автомат (FSM) — ловушка для программиста

Уровень сложностиПростой
Время на прочтение5 мин
Охват и читатели13K
Всего голосов 13: ↑7 и ↓6+3
Комментарии55

Комментарии 55

ЗакрепленныеЗакреплённые комментарии

Я машины состояний встречаю прежде всего во встраиваемых системах. Это довольно мощный инструмент создания асинхронных процессов, без реальной многозадачной ОС.

Как пример - TCP.

А еще можете ознакомится с такой штукой:

https://www.state-machine.com

Однако это не догма, если задача решается как у вас то почему нет?

Я бы тоже хотел чтобы кто-то хотя бы попытался сформулировать смысл не только самого конечного автомата, но и смысл его применения в коде.

Главный вопрос, по моему, чем нам помогает то что мы ограничиваем свое представление о программируемой-проектируемой системе конечным числом дискретных состояний, например, в рассмотренной задаче мы забываем про периоды времени которые тоже важны, по моему. Мне кажется это и есть своеобразная ловушка для программиста, то что попытка свести проектируемую систему к конечному автомату заставляет игнорировать важные НЕ-дискретные параметры системы, которые в конечном итоге все равно каким-то образом присутствуют в решении, но каким- то самым нелепым образом в виде костылей и заплаток для прекрасного конечного автомата.

Статья напоминает типичную аргументацию противников ООП. В духе "Вот смотрите, давайте предположим что нам нужно сложить два числа. Без ООП мы напишем а+б и получим ответ, но с ООП мы создадим класс числа, инкапсулируем значение, перегрузим оператор сложения и только после этого сложим числа. И кому нужны такие сложности?". И типо естественно на искусственном примере, который мало того что элементарный, но и буквально подогнан специально для доказательства конкретной точки зрения, будет работать выбранная концепция, какой бы она ни была. Аргументации натащить не проблема. Вот только в реальном программировании у вас не будет три/пять состояний с одним переходом на каждое. У вас будет система с пятнадцатью состояниями, реализующими свои сегменты алгоритма со сложной логикой переходов. И там уже любые способы упростить количество внешнего влияния на локальный сегмент алгоритма будут снижать сложность на порядки, будь то инкапсуляция или использование машины состояний. Но нет, зачем учить пропись, если "мама" можно печатными буквами написать и водители фур лохи, зачем так далеко ездят, если за любыми покупками можно пешком в пункт выдачи маркетплейса под домом сходить.

все бы было хорошо с вашей претензией, если бы это был действительно совсем искусственный пример. На самом деле это настоящее решение реальной задачи в обобщенном виде (в псевдо-коде). Задача действительно выбрана простая, но как минимум для класса простых задач это решение замечательно работает. Это очевидно, может раздражает как раз то насколько это очевидно? Для сложных задач можно применить декомпозицию и может оказаться что сложные задачи тоже состоят из простых подзадач.

Я может быть не очень внимательно читал. Но исходный посыл что про не нужность и искусственность стейт машин из статьи как-то не следует. Вот это

Оказывается, если правильно разделить описание системы на список данных по состояниям этой системы и процедуру, которая позволяет переключать эти состояния по событиям, которые происходят в системе то программа получается намного проще.

По сути описание конечного автомата - то есть системы с конечным числом состояний и переходов. А уж как его реализовывать в каждом конкретном случае это к самой концепции конечного автомата отношение не имеет.

И да прежде чем начинать разработку желательно проанализовать задачу. Возможно имеет смысл сделать автомат для лампочки например с состояниями горит, не горит, мигает и сломалась и отдельный для светофора с состоянми: красный,переключение на зеленый, зеленый ,переключение на красный и мигающий желтый (не исправность. )

тут есть один хитрый аспект, мне кажется, который я (да!) не смог сформулировать-раскрыть во всей его неоднозначности. Если состояние задается, как для этого случая, например, тремя битами для выключателей плюс скажем 16-ть битов (short) значение задержки, итого получается два в 19-й степени возможных состояний. Это все еще конечное число состояний? То есть имеет ли смысл рассматривать такое большое число состояний как конечное?

Есть предел когда конечный автомат перестает быть конечным? И если этот предел существует, что там за пределом? По моему пустота не познанного пока.

Или можно с другой стороны зайти. Три бита для выключателей действительно задают конечное число состояний, как не крути, но когда они идут в комплекте со значением , которое по своему характеру не дискретное (хоть и приводится к определенному разрешению и значит дискретности) этот комплект это все еще дискретное состояние или уже не дискретное?

А приведите, пожалуйста, пару задач, в которых FSM действительно нужна.

На фундаментальном уровне процессор (CPU) — это сложный конечный автомат (Finite State Machine, FSM), который управляет потоком данных и выполняет инструкции, переключаясь между предопределенными состояниями.

Изначально конечные автоматы появились именно применительно к CPU которые были достаточно примитивными чтобы оставаться в рамках этой теории конечных автоматов. Но современные процессоры настолько сложны и сочетают в себе столько разных технологий, что применительно к ним про FSM уже давно никто не вспоминает. Даже одно понятие конвейер уже не укладывается в рамки теории конечных автоматов.

Даже одно понятие конвейер уже не укладывается в рамки теории конечных автоматов.

– докажите. Очень, очень сильное утверждение.

То есть, FSM нигде не нужны толком, я правильно понимаю?

сколько состояний есть у конвейера который способен обрабатывать примерно 100 (сто) типов команд (оценка минимального размера списка инструкций ассемблера)? и где половина и этих команд имеет параметры из достаточно больших непрерывных диапазонов. При чем эти команды конвейером разделяются на скажем 4 фазы и накладываются, соответственно со сдвигами,то есть все это богатство фактически возводится в какую-то степень.

Вы считаете есть какой-то смысл рисовать-строить-анализировать это как стейт-машину с миллиардами состояний?

 Если состояние задается, как для этого случая, например, тремя битами для выключателей плюс скажем 16-ть битов (short) значение задержки, итого получается два в 19-й степени возможных состояний. Это все еще конечное число состояний? То есть имеет ли смысл рассматривать такое большое число состояний как конечное?

Вы либо никогда в реальной жизни не использовали FSM, либо просто прикалываетесь.

Почти во всех реализациях FSM можно передавать payload вместе с переходом, если уж так сильно надо, и это никак не связано с количеством возможных состояний.

Если задержка не влияет на поведение переходов, то и передавать не обязательно, и она будет учитываться где-то в другом месте, там где реализована ф-я мигания. Вы же сами решаете, что именно моделирует ваш автомат

под конечностью подразумевается что число возможных состояний явно указано. Сколько таких состояний не важно с точки зрения автомата как мат модели. С точки зрения реализации могут быть варианты. В том числе рассчитанные на очень большое число состояний. Например состояния хранится в таблице бд. Если оперировать не дискретными значениями то либо вы их дискретизируете и задача сводится к решенной, либо да конечный автомат не походит к задаче. Просто по определению - множество состояний будет не счетно.

Сделать входной коннектор для LLM ,написать "Теперь ты светофор,не убей никого" и будь что будет 🤣🤣

Помоему вы сейчас стейт паттерн переизобретете

это запрещено?

Вообще то это я не сейчас изобретаю, я давно этим пользуюсь, просто решил опубликовать как интересную-полезную технику реализации определенного класса задач. Спасибо всем кто помогает мне это формулировать!

Я машины состояний встречаю прежде всего во встраиваемых системах. Это довольно мощный инструмент создания асинхронных процессов, без реальной многозадачной ОС.

Как пример - TCP.

А еще можете ознакомится с такой штукой:

https://www.state-machine.com

Однако это не догма, если задача решается как у вас то почему нет?

Если кратко сформулировать содержание то получим "те же яйца, только с боку".

Очевидно, автор легко продолжит серию публикаций статьёй "Почему нахрен не нужна нормализация таблиц БД и язык SQL", рассказав, что если есть данные: имя, должность, кабинет, телефон, эл. почта для 3 сотрудников, то это можно записать в переменные и быстро—быстро извлекать эти значения из ОЗУ.

Ждём.

Примерно так и появился Redis ))

Статья выглядит как пример в защиту вполне очевидного тезиса “Не для всех задач подходит state machine”.

Напомнило статью Как два программиста хлеб пекли https://habr.com/ru/articles/153225/

Статья как раз и описывает конечный автомат в виде таблицы.

То есть автор отказывается писать конечный автомат в одном виде и записывает его в другом... и просто не понимает этого.

Это не совсем конечный автомат в виде таблицы. В таблице одно из измерений (например, строки) - это состояния, а другое (пусть столбцы) - это события, их пересечение указывает на следующее состояние. Поскольку автор выбрал пример, где из каждого состояния возможен лишь один выход, и у каждого состояния только один вход, то у него измерение событий пропало, и следующее состояние тупо в строке ниже. Такой вырожденный случай очень плох как иллюстрация КА, но автора это не смутило.

Интересно, а никого (включая автора) не смутило, что конечный автомат светофора на рисунке неверно составлен в исходной статье?))

Конечно, каждая задача должна решаться с применением наиболее подходящих инструментов. Теория и алгоритмы конечных автоматов очень много где пуспешно рименяются, например, в системах массового обслуживания.

В данном случае неудачный пример в исходной статье напоминает закручивание шурупов выключенным шуруповёртом как отверткой. А автор, пользуясь этим, склоняет некрепшие умы к тому, что математические методы не стоит применять в программировании, т.к. это слишком сложно. И где-то он безусловно прав, т.к. увлечение новомодными инструментами и паттернами уводит многих разработчиков в сторону, заставляя тратить время на архитектуру в проектах, которые функционально ограничены. И уж точно автор прав в том, что задачи архитектора, инженера, программиста очень сильно разичаются и требуют разных подходов и инструментов. Но теорию учить и применять надо!)

Всё примеры программ, приведённые автором, являются реализациями конечного автомата. Есть состояния и условия переходов. Нейросетей не вижу. Как то так

Если оставить за скобками брюзжащую и умничающую манеру автора, то в статье он взял вырожденный пример, в котором из каждого состояния возможен только один выход, развернул этот пример линейно-циклично и громогласно объявил о своей победе. Светофор - самый тупой пример КМ, который можно было взять.

Разве смысл конечного автомата не в том что он переходит из состояния в состояние по какой-то логике, по каким-то событиям и условиям? Простому тупому светофору действительно ни к чему FSM, его можно собрать на кулачках и эксцентриках. А вот если добавить разную длительность сигналов в разное время суток, кнопки для пешеходов, постоянный желтый с 2 до 6 утра, возможность централизованно включить зеленый или красный для экстренных случаев, вот тогда наверное конечный автомат пригодится.

И это снова объясняет, почему функциональное программирование без стейтов, как в ооп или процедурном, годится только для проектов уровня hello world

Чистые функции это прекрасно, но где-то, рано или поздно, им приходится сталкиваться с грубым реальным stateful-миром.

Я бы тоже хотел чтобы кто-то хотя бы попытался сформулировать смысл не только самого конечного автомата, но и смысл его применения в коде.

Главный вопрос, по моему, чем нам помогает то что мы ограничиваем свое представление о программируемой-проектируемой системе конечным числом дискретных состояний, например, в рассмотренной задаче мы забываем про периоды времени которые тоже важны, по моему. Мне кажется это и есть своеобразная ловушка для программиста, то что попытка свести проектируемую систему к конечному автомату заставляет игнорировать важные НЕ-дискретные параметры системы, которые в конечном итоге все равно каким-то образом присутствуют в решении, но каким- то самым нелепым образом в виде костылей и заплаток для прекрасного конечного автомата.

Вот так вот мы выяснили что у одной управляемой лампочки может быть больше состояний чем у целого светофора!

То есть такие состояния светофора как "никакая лапочка не горит", "все лампочки горят", "две лампочки горят", "она лампочка мигает, другие горят" и т.д. мы не учитываем сознательно, для красоты слога?)

вЫключили лампочку

Не ожидал увидеть прописную букву (Ы) в середине ряда строчных слова, сокращённом в новоязе (со времён входа России в ВТО), когда переводчики ISO-стандартов нарушили исконно-русско-электротехническое и ввели сокращение "Выкл" вместо "Откл")

ЗЫ: ну, до 2008-го, как-то воспитывал электротехнический персонал промэлектроники (через выдачу Предписаний за нарушения правил обслуживания технологического оборудования на пром.производстве") правилам русско-технического языка по нанесению нетираемого обозначения на вводных автоматах обозначений "Вкл/Откл", принятых со времён развитого социализма... Но, после 2009-го, пришлось убогоиться и отдохнуть от мысли, когда стали легитимно гулять обе пары, как "Вкл/Откл", так и "Вкл/Выкл", на данный момент заместивший первоначально-исконный вариант.

Ну, это, так, для красного словца)

А, что касаемо темы светофора, мне повезло в 2008 принять участие в апробации внедрения трехцветной RYG-светофор-палитры в управленческих принятиях решений (под чутким руководством англосаксев на пути ко вхождению в ВТО через сертификацию на iso_2009), как переходного этапа входа в визуализацию статусов управленческих решений на основе майкрософтской/четырехцветной/RYBG- палитры, как промежуточного звена на пути темных фабрик (через трёхцветную RGB-палитру)))

Когда я сталкиваюсь с эмбедед разработчиками - они не могут взять лучшее от мира it и АСУТП. Хотя оба мира уже давно прожевали fsm и разобрались с ними очень давно.

Почему автор бизнес логику смешивает с логикой работы с железом io? Это в обоих мирах очевидно , или только в ембедеде это ноухау?

У автора проблемы начинаются с корректностью постановки задачи. А дальше идут какие-то вырожденные фантазии.

Статья пляшет вокруг априори некорректной диаграмма состояний. Светофор - это не "красный-желтый-зеленый". Это, как минимум, мигающий желтый, мигающий зеленый, а ещё не только таймеры как триггер перехода. Сорри, ощущение совы, натянутой на глобус...

Сама по себе описанная модель светофора и реализуемого для него FSM кривая. Такое ощущение, что автор вообще никогда не разрабатывал FSM и пытается изобрести какой-то костыльный велосипед, чтобы показать, что FSM - это плохо. А по факту разарабывает очень кривой FSM, который меняет состояние по таблице.

Признак хорошего FSM:

  • наличие единственной переменной (enum), хранящей информацию о текущем состоянии автомата;

  • наличие контекста - специальной структуры данных, содержащей достаточную информацию для работы автомата;

  • переход между состояниями осуществляется с помощью обработки асинхронных событий (получение сообщения, срабатывание таймера).

Язык SDL, разработанный ITU, как раз, заточен на описание конечных автоматов, на которых базируются все телекоммуникации (как минимум, прошлых поколений). Как только автор постигнет SDL, сразу поймёт, как следует описывать и писать конечные автоматы правильно.

Конкретно в данной статье мы имеем очень кривой светофор, который вообще не имеет ничего общего с реальным миром. Тут дла начала следует матчасть в виде ПДД по-хорошему подучить, прежде чем садиться за разработку светофора.

Максимально приближенному к реальности светофору достаточно обладать следующей информацией:

  • текущее состояние: выключен (совсем не работает), отключен (работает в режиме мигающего жёлтого, не регулирует перекрёсток), разрешающий сигнал (горит зелёный), завершение разрешающего сигнала (мигает зелёный), предупреждающий сигнал (горит жёлтый), запрещающий сигнал (горит красный), переход на разрешающий сигнал (горит красный с жёлтым), аварийный режим (когда возникла какая-то ошибка в аппаратуре и без помощи обслуживающего персонала уже не обойтись);

  • взводимый таймер на изменение состояния;

  • тайминги нахождения светофора в каждом состоянии;

  • счётчик переключений в контексте текущего состояния (когда надо показывать мигающие сигналы).

Вы спросите, а где же состояние лампочек? А оно не нужно, оно легко выводится из параметров "текущее состояние" и "счётчик переключений", пусть состояние лампочек в виде битовой карты хранит контроллер, который этими лампочками, по сути, и управляет.

Признак хорошего FSM:

  • наличие единственной переменной (enum), хранящей информацию о текущем состоянии автомата;

Получается нужна божественная переменная (enum), которая хранит состояние автомата, то есть всей системы?

наличие контекста - специальной структуры данных, содержащей достаточную информацию для работы автомата;

вот мне кажется что обычно происходит путаница в том что является контекстом, а что состоянием. Как вы разделяете что является контекстом, а что собственно состоянием? Есть четкий критерий на все случаи жизни?

А вот это я действительно упустил:

переход на разрешающий сигнал (горит красный с жёлтым)

я очень давно такого не видел, и забыл что так бывает! Но что характерно, в моем псевдо коде ничего менять не придется в этом случае, надо изменить/добавить только строчку в таблице, примерно так:

красный : 20 секунд
красный + желтый : 5 секунд
...

Вместо:

красный : 20 секунд
желтый : 5 секунд
...

Интересно как это будет выглядеть на языке SDL, разработанном ITU ? Неужели можно сделать проще?

Получается нужна божественная переменная (enum), которая хранит состояние автомата, то есть всей системы?

На то он и автомат, потому что finite STATE machine. Описывать автомат конкретными состояниями и диаграммой перехода между ними проще, чем иметь размазанное в нескольких переменных состояние. Ещё раз - смотрите в сторону SDL, где любая диаграмма начинается с блока, обозначающего текущее состояние.

вот мне кажется что обычно происходит путаница в том что является контекстом, а что состоянием.

Переменная состояния сама по себе может быть частью контекста. Конкретный пример FSM - OpenGL. Вы создаёте OpenGL context и вызываете последовательно функции, которые меняют его состояние. Это абсолютно аналогично тому, как в телекоммуникациях какой-нибудь прибор, обслуживающий систему, принимал некие управляющие сообщения и обрабатывал их.

я очень давно такого не видел, и забыл что так бывает!

Это видит каждый, кто ездит за рулём, и происходит это постоянно. Есть правила дорожного движения, строго регламентирующие сигналы светофора, и отталкиваться при проектировании реальной системы нужно было от них.

Интересно как это будет выглядеть на языке SDL, разработанном ITU ?

Примерно вот так будет диаграмма выглядеть для двух состояний: зелёного и мигающего зелёного.

https://ibb.co/QyjcmPM

Особенность такой диаграммы в том, что она учитывает, что светофор может в любой момент быть переведён в выключенный режим (пришёл сигнал о выключении) либо в режим, когда светофор не обслуживает перекрёсток (мигающий жёлтый). В вашем примере FSM не предусматривает переход в другое состояние по приходу какого-либо внешнего сигнала, а строго идёт по таблице, т.е. работает в режиме интерпретатора.

Потом эта диаграмма очень легко переводится на любой язык программирования.

В диаграмме забыл "да"/"нет" ветки подписать. Думаю, разберётесь, какая "да", а какая "нет".

а что-то не открывается ссылка у меня из Нижнего Н. Висит! Не разберусь.

Должна открываться. Попробуйте трёхбуквенные сервисы.

В другом браузере загрузилась, но тоже с задержкой (у меня почему-то тормозит). Красиво:

Интересно как и какой код получится сгенерировать по такой схеме.

Код в таком случае выглядит как обычный event loop, который обрабатывает поступающие в него входящие события (команды, таймер). При получении очередного события определяется текущее состояние автомата и делегируется обслуживание сообщения соответствующей функции, отвечающей за логику обработки событий в конкретном состоянии автомата. Если говорить применительно к нарисованной картинке - то у нас будут две функции. Первая обрабатывает состояние светофора "Зелёный", вторая - "Мигающий зелёный".

А если еще нужен мигающий желтый, будет точная копия функции мигающий зеленый только на другой бит цвета и с другими(возможно) хардкодными задержками?

Ну хардкод здесь просто для упрощения понимания. Ничто не мечтает параметризовать все тайминги и счётчики. Точно так же код можно переиспользовать при обработке событий "выключение" и "отключение", вынеся в отдельные функции.

Сила SDL в том, что там сразу для каждого состояния видно, какие события при каком состоянии заставляют автомат работать и менять состояние на новое.

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

красный => жёлтый => зелёный => жёлтый => красный (выход на новый цикл)

Так вот, такую последовательность 3-мя состояниями не реализуешь.

- - -

Далее, прежде чем что-либо рисовать, должно определиться с требованиями. Люди хорошо знающие ПДД, возможно могут и по памяти восстановить как там светофор должен работать, но раз мы говорим о примере, то эти требования желательно было огласить изначально, перед тем как выбирать метод их реализации. Дабы решение задачи было приведено с самого начала. И дабы помочь тем кто не так хорош в ПДД (а заодно и явно очертить тот объём, который в примере реализовать собрались).

.

И что-же будет, если мы возьмёмся эти требования перечислять?

А будет нечто подобное картинки из комментария выше (была ссылка и её потом вставили картинкой).

Я имею в виду, что регламент будет говорить что-то вида:

  • если у нас перекрёсток из двух дорог (а их может быть несколько видов перекрёстков) то, когда на одной из них красный, на другой должен быть зелёный

  • если мы фиксируем отсутствие свечения одного из возможных сигналов — светофоры перекрёстка должны перейти в аварийное состояния (мигание жёлтого) либо вообще выключится — и тогда, в рамках ПДД проезд будет регулироваться установленными знаками (если есть) либо правилом “помехи с права”.

  • последние n-секунд перед переключением на следующий сигнал светофора текущий сигнал должен мигать (сообщая что вскоре будет смена сигнала)

  • и т.д.

.

Другими словами, техническое задание будет описывать связку из (ситуация, событие, реакция). При этом, это конечное множество (от тех факторов и их сочетаний, которые учли) и в некотором смысле “замкнутое” множество — в том смысле что оно должно учесть всё возможное в рамках конечного числа выделенных факторов.

Т.е. само техническое задание введёт:

  • набор параметров, которые нужно учитывать

    • например: какой сигнал сейчас включен, который сейчас час, какие внешние команды получены, каково состояние ламп светофоров, признак наличия прерывания от таймера, время подачи каждого из сигналов, остаток времени текущего сигнала.

  • возможные значения параметров

    • например: сигнал может быть: включен, отключен, мигать (как вариант включенного)

  • множество ситуаций заданное всеми возможными комбинациями значений параметров — которых может быть много, но число которых в некотором смысле конечно. В некотором смысле значит, что можно различать настройки (как долго каждый из сигналов включать), и сам факт включения одного из сигналов на заданное настройками время.

  • возможные события в каждой из ситуаций

    • например: подавали зелёный сигнал, на середине его времени перегорела лампа зелёного, он исчез — светофор должен перейти в аварийный режим (мигание жёлтого либо вообще выключен)

  • требуемая реакция для каждой пары состояние-событие

.

И вот после перечисления требований (технического задания) встанет вопрос — а каким инструментом/подходом их реализовывать?

- - -

Тот факт что веб разработка прогибается под требование бизнеса вида: дайте нам результат быстро, вот этот сценарий, а другие потом — нельзя назвать её преимуществом. А за попытку тот-же подход перенести в область повышенной опасности (дорожное движение из примера) пожалуй административную (а может и уголовную) ответственность нести должно.

Тот факт что веб разработка прогибается под требование бизнеса вида: дайте нам результат быстро, вот этот сценарий, а другие потом — нельзя назвать её преимуществом.

По моему софтовые решения на то и софтовые, то есть мягкие, чтобы прогибаться под требования бизнеса. Когда надо делать монолит на века лучше делать из чистого мрамора :) .

А если серьезно, мне кажется тут просто слово подобрано не правильно! Если слово "прогибаться" заменить словом "адаптироваться" то ваша фраза из негативной становится вполне позитивной, оцените:

Тот факт что веб разработка (на самом деле любая софтовая разработка) АДАПТИРУЕТСЯ под требование бизнеса вида: дайте нам результат быстро, вот этот сценарий, а другие потом — нельзя НЕ назвать её преимуществом.

Разработка решений в виде ПО (софта) должна в том числе служить задачам проверки и отработки новых решений на практике. Как же еще проверять-отрабатывать новые решения как не на базе концепции адаптации софта?

Хочется посмотреть как вы без FSM накодите, скажем, логику игрового персонажа. У меня ушло 2к строк, прежде чем я осознал что пора эту жуть, наполовину состоящую из ифов проверяющих условия, кардинально менять.

хотелось бы посмотреть что нужно от игрового персонажа, как вы определяете для него логику.

Я вот смотрю пример реализации на конечных автоматах : Листинг 5. Код класса светофора

и я что-то не вижу что FSM избавляют от ифов, а у меня то как раз от ифов получилось избавиться в решении, заменив их на работу по таблице. А у вас как получилось исключить IF-ы? Можете поделиться идеей?

А зачем вы смотрите на светофоры? Вот, к примеру, пример схемы передвижения игрока:

Без FSM у меня была куча ифов, которые по каждому поводу проверяли кучу параметров, типа стоит ли игрок на земле, как быстро двигается, держит ли оружие, и т.д.; причем они были разбросаны повсюду, поскольку эти проверки надо было делать во множестве мест. В итоге оно работало крайне эзотерически, и никогда не было понятно, почему что-то перестало работать как надо, ведь код для этого состояния расчленен и разбросан по всему классу. Ну и да, код длиной в 2000 строк в котором просто тупо теряешься.

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

И это только одна логика управления игроком. А вот, к примеру, Behavior Tree (Что, по сути, технически просто FSM cо стандартизированными условиями переходов и очень гранулированными стейтами) для простого непися:

(В случае непися я решил взять фреймворк для отображения данных в виде нод, и основать BT уже на нем, ибо мне была нужна воможность собирать BT в движке из готовых блоков для разнообразия поведения неписей)

так это у вас иерархия наследования типов движения, то есть какой тип является подтипом какого, по моему. Стейт машина была бы если бы вы нарисовали из какого состояния движения можно переходить в какие другие состояния движения. Например что из walk возможен jump. Неужели вы спутали иерархию типов(которые в этом случае тоже состояния) с переходами между состояниями?

На картинке просто количество стейтов и как они друг с другом логически связаны. В коде все переходы, разумеется, присутствуют, по типу

        protected override void TrySwitchState()
        {
            if (canUncrouch && sm.movementInput.y > -0.01f)
            {
                SwitchToState<Walk>();
            }
        }

Ну то есть вы буквально заменили конечный автомат на табличный конечный автомат, чтобы доказать, что конечные автоматы не нужны?

а что вы называете табличным автоматом, собственно, таблицу или код который работает по этой таблице? В этом коде нет понятия состояние мне кажется, есть параметры системы которые загружаются, вообще говоря они могут меняться, это не enum! Таким образом отсутствует конечность (в смысле ограниченность).

Табличным автоматом я называю саму абстракцию, которая реализуется таблицей и интерпретатором (данными и кодом по сути).

В коде нет понятия состояния, но оно существует неявно и задаётся индексом. Замените индекс на enum, дайте ему именованные значения - вот вам и переменная "состояние". То, что вы заменили набор свитчей и ифов таблицей - ну прекрасно, это лишь способ представления или реализации.

"Конечность" подразумевает конечность состояний. Очевидно, что у конечной таблицы оно конечно. Замена таблицы на другую создает другой конечный автомат, ну так вы можете задать их целое семейство, в котором каждый автомат будет конечен.

И как вам правильно замечали, у вас не самая удачная абстракция - именно потому, что вы старались уйти от модели FSM, реализуя ее неявно. Состояние лампочек светофора не эквивалентно состоянию автомата, можете считать их маппингом, визуализацией условно говоря состояния автомата. Состояние автомата - это строка в таблице, лампочки и период - атрибуты состояния. У вас "зелёный" встречается трижды - и это три разных состояния автомата, анализируя горит сейчас зелёный или не горит мы состояние автомата (строку таблицы) не получим. А вот переход между строками таблицы (между состояниями автомата) или само это состояние может сопровождаться какими угодно эффектами.

Состояние "можно ехать" - включи зеленую лампу. Состояние "готовься" - моргай зелёной лампой. Только получается что нужно два таймера - один управляет КА, второй - анимирует лампу, запуская моргание. Можно просто запустить какой-нибудь setMode(green, 'blink', 1000); setMode(red, 'off'); setMode(yellow, 'off');

А можно упороться, если поведение отдельного состояние сложное и превратить его в КА в свою очередь, получив иерархический КА. То есть переход в состояние "готовься" запускает дочерний КА с его собственным набором состояний, если хотите. И это тоже КА с конечным набором состояний, композиция КА конечна.

Так что вы просто перепутали слой отображения со слоем логики, интуитивно придя к той же самой модели КА в табличной форме.

То, что вы заменили набор свитчей и ифов таблицей - ну прекрасно, это лишь способ представления или реализации.

Так у меня цель получить вот это "ну прекрасно"! Мне надо заменить набор свитчей и ифов простой таблицей. Все ваши дальнейшие рассуждения о том как построить прекрасный конечный автомат к задаче отношения не имеют, по моему. По моему не важно, что:

У вас "зелёный" встречается трижды - и это три разных состояния автомата, анализируя горит сейчас зелёный или не горит мы состояние автомата (строку таблицы) не получим.

А зачем нам получать строку таблицы (индекс ее видимо вы имеете ввиду) когда строка у нас уже получена для того чтобы проинициализировать из этой строки состояние системы, заметьте полное состояние, которое может быть сколь угодно сложным? Действительно индекс это и есть состояние. Я вам больше скажу из таких таблиц можно и граф построить (граф это все уникальные ветки этого графа в которых есть ссылки на переходы на присоединенные ветки) и в каждую запись можно добавить уникальный ID этой записи (здесь просто индекс как вы верно заметили) который и будет фактически ID соответствующего состояния. Только зачем нам нужен этот номер фактически состояния, когда мы сразу получаем ссылку (указатель) на полную структуру со всеми значениями состояния?

Именованное состояние служит двум целям.

  1. Семантическая - мы можем четко назвать некоторое состояние для различения с другими. Представьте таблицу побольше, представьте что какое-то новое состояние добавили или убрали - поведение строки №3 или №5 может уплыть, если ориетироваться просто на порядок.

  2. Индексная - тот самый указатель за которым скрывается поведение - некоторая единица кода. Вам повезло, что в вашей модели поведение меняется циклически, вы можете переходить от строки к строке последовательно, возвращаясь к началу в конце. То есть это последовательный переход между состояниями. Представьте, что где-то нужно перескочить через строку или вернуться назад на две - все, вы неизбежно переходите к именованным состоянием.

Ну и главное - ваш основной посыл - конечные автоматы не нужны, вот пример, где они не работают. Вы начинаете пример с построения неудачной модели конечного автомата, создав неудачную абстракцию, в которой состояние светофора нужно вывести через состояние его лампочек. После этого вы создаете другую абстракцию, которая представляет собой ДРУГОЙ конечный автомат без именованных состояний с простым циклическим переходом между ними. Возможно ли такое упрощение? Вполне. Но как только задача будет усложнена, вы вернете назад все то, что заботливо скрыли.

То есть весь пост можно свести к "линейному FSM не нужен явный синтаксис состояний, поэтому он может быть представлен в табличной форме".

Ну и главное - ваш основной посыл - конечные автоматы не нужны, вот пример, где они не работают.

Да нет! Это ваш основной посыл меня в чем то обвинить, мне кажется. Личная неприязнь? Они замечательно работают, но для некоторых задач есть более эффективная техника программирования и вы, по моему, для этого, конкретного простого примера это даже признаете.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации