Русский
Русский
English
Статистика
Реклама

Factorio

Перевод Совместная игра в Factorio лучшее техническое собеседование, что мы проводили

09.04.2021 14:13:02 | Автор: admin
В последнее время много копий сломано вокруг технических собеседований. Очевидно, что инвертирование двоичного дерева на доске практически никак не связано с практическими навыками реального программиста. Примитивный Fizzbuzz по-прежнему остаётся самым эффективным тестом. Как следствие, выросло внимание к опенсорсным проектам, но оказалось, что это тоже не очень хороший показатель, потому что у большинства профессионалов нет на них времени.

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

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

Factorio?


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

Начинаете с нуля. Добываете вручную железную руду и камень, строите плавильную печь, чтобы плавить руду в железные пластины, из которых можно скрафтить автоматический твёрдотопливный бур. Можно самостоятельно забирать железную руду из бура и закладывать её в плавильню, но более эффективно использовать автоматический конвейер. Потом можно использовать полученное железо, чтобы сделать другой бур, который автоматизирует добычу угля. Затем строится конвейер для забора угля и конвейер для его передачи в бур. Эта крошечная фабрика производит железные пластины, из которых можно сделать третий бур и начать добычу медной руды, которая позволяет крафтить медные плиты, а из них можно сделать медный провод, необходимый для работы погружного насоса. В сочетании с паровым котлом и паровым двигателем это даёт нам первую электроэнергию. Её можно использовать для исследовательского центра и изобретения новых технологий, таких как сборочный автомат. Как только разблокируете сборочные автоматы, можете использовать сделанный вручную провод для создания сборочной машины, которая будет автоматически изготавливать эти провода.

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

Выбор направления


Игра начинается без установленной цели и почти без направления. Ведущий программист должен быть способен изучить UI и определить цель, а затем разработать план для её достижения. От джуниора ожидают правильного выполнения задач, поставленных ведущим разработчиком. Предполагается, что стажёр работает с наставником, но джуниор должен быть в состоянии самостоятельно устранить основные проблемы со своим кодом, прежде чем просить помощи от старшего. Миддл должен уметь работать независимо, как только ему дают задание, но от него не ожидают архитектурного проектирования.

Конкретные ожидания можно сформулировать так:

  • Стажёр, как правило, должен быть в состоянии разместить чертёж и соединить его с чем-то ещё, например, с месторождением руды.
  • От джуниоров ожидают самостоятельного строительства производственной линии, пусть и не оптимальной. Им может понадобиться помощь старшего разработчика в том, как правильно направить конвейеры ко всем промежуточным сборочным автоматам.
  • Как только получено задание, миддл должен спроектировать почти оптимальную производственную линию (без маяков) с минимальным контролем.
  • Ведущий программист не нуждается в руководстве, способен сам определить цели и разработать план действий, а затем делегировать эти задачи другим программистам.

Командная работа


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

Если игрок уйдёт в себя, начнёт делать всё сам или молча исправлять проблемы, это быстро навлечёт на него гнев команды по тем же самым причинам, по которым коллеги злятся на программистов-ковбоев. К счастью, в Factorio есть встроенный эквивалент git blame: он показывает последнего игрока, который изменил любую сущность. Таким образом, если кто-то поставил костыль и не сообщил команде о проблеме, то когда этот костыль наконец сломается все узнают, кто виноват. Если хотите выиграть, придётся плотно сотрудничать с товарищами по команде.

Отладка


Одним из главных навыков для программиста является отладка. Это, пожалуй, самая очевидная параллель между Factorio и реальной разработкой ПО. Что-то может сломаться очень далеко от фактического источника проблемы. Способность быстро выяснить реальную проблему это критический навык, и процесс мышления почти идентичен отслеживанию причины сбоя в реальной программе. Если перестал работать сборочный автомат, сначала нужно проверить выходящие потоки. Потом проверить, какого ингредиента не хватает на входе. Затем проследить ингредиент по фабрике, чтобы узнать, где он производится. И повторять процесс снова и снова, до тошноты.

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

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

Подобные ситуации позволяют Factorio точно сымитировать сложные взаимозависимости, с которыми обычно имеют дело программисты. Сложность возрастает по мере добавления в геймплей новых концепций. Это очень похоже на рост сложности от дополнительных уровней абстракции в отладке сбоя, который мог произойти глубоко внутри одного из используемых вами фреймворков.

Код-ревью


Часто первоначальный дизайн нужно исправить для повышения производительности или пропускной способности. Хорошие программисты не только примут критику своих чертежей, но и учтут её в будущей работе. Если они не согласны с изменением, то предоставят конкретное объяснение, чтобы команда могла более точно обдумать плюсы и минусы предлагаемого изменения.

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

Стиль написания кода и фреймворки


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

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


Логистическая сеть

Есть разные способы построения логистических сетей. Самый простой с сундуками пассивного снабжения. Но есть другой метод сундуки хранения с фильтром, который решает проблему с мусором. Оба метода требуют правильной установки ограничителей в нужных местах. Сундуки пассивного снабжения обычно ограничены пространством сундука. К сундукам хранения нужно поставить манипулятор для подключения сундука к логистической сети. И обеспечить не менее N предметов перед установкой манипулятора. Если забыть про эти шаги, то будут впустую потрачены огромные ресурсы. Если программист постоянно забывает про ограничители на выходах это красный флаг, что человек небрежно относится к производительности в реальных приложениях.

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


Неоптимальный дизайн завода на дронах, источник

Многопоточность


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

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

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

Масштабирование


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

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

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

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

Микросервисы и модули


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

Мегабазу на основе поездов иногда называют дизайном городских кварталов (city-block), где поезда вокруг кварталов завода контролируют все входы и выходы. Таким образом, каждый отдельный квартал изолирован от всех остальных, поскольку все входные данные чисты в том смысле, что они поступают из железнодорожной сети. Это почти идентично архитектуре микросервисов (по HTTP) или межпроцессному взаимодействию (IPC), с аналогичными потенциальными проблемами из-за задержек I/O, поскольку результаты не могут поступать постоянно, они должны передаваться в пакетах или поездах по железнодорожной сети.

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

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

Распределённые системы


Space Exploration полностью переделанная версия Factorio для колонизации космоса. Здесь планеты становятся ограниченными ресурсами, требуя от игроков колонизировать другие миры и использовать ракеты для передачи ресурсов между планетами. Из-за огромной задержки с доставкой материалов между планетами, координация этих баз приводит к возникновению проблем, сходных с глобально распределённой системой баз данных. Даже в логической сети приходится бороться с задержкой, потому что автоматическая система теряет из виду элементы, которые запущены, но ещё не достигли целевой планеты. Если это не учитывать, возникают дубликаты запросов для всех нужных элементов. С точно такой же проблемой сталкиваются распределённые системы при попытке обеспечить согласованность между узлами.

Вывод


В целом софтверная индустрия не имеет ни малейшего представления, как находить и нанимать лучших разработчиков. Наверное, совместная игра в Factorio стала лучшим техническим собеседованием, которые мы когда-либо проводили. И это нас сильно смущает. Такое собеседование дико непрактично, занимает более 20 часов в мультиплеере с первого раза или 8 часов для команды опытных игроков. Что из этого можно извлечь? Не знаю. Мы, конечно, не можем перейти на Factorio в качестве метода собеседований с таким же успехом можно просто дать кандидату домашнее задание.

Но это уже лучше, чем собеседование на доске.
Подробнее..

Перевод 8-битный Тьюринг-полный компьютер в Factorio

15.04.2021 12:10:33 | Автор: admin

Хочу поделиться своим проектом, созданным в Factorio на основе предлагаемой этой игрой логики. На этот проект меня вдохновил великий ум, записавший пошаговое руководство по созданию практически такой же машины, но в реальном мире. Рекомендую посмотреть его, оно поможет вам понять и воссоздать этот проект: 8-bit computer

Я преклоняю голову перед Беном Итером, с помощью своего канала научившему меня столь многому, и хочу посвятить этот небольшой проект ему. Отличная работа, Бен!

Вот компьютер, вычисляющий число Фибоначчи, после превышения лимита 8 бит (числа 255) он выполняет условный переход и начинает заново:

image

Давайте разберёмся, как работает этот компьютер. И не бойтесь уверен, что, разобравшись с основами, вы тоже сможете его сделать! Начнём с общей схемы компьютера. Здесь я выделил важные области. Ниже я объясню, как создал их.

CLK таймер, обеспечивающий синхронизацию машины. Современные ЦП способны работать с частотой 4-5 ГГц (4-5 000 000 000 Гц). На данный момент моя машина может работать с частотой 2 Гц из-за ограничений логических затворов Factorio каждый ввод должен вычисляться для каждого комбинатора (затвора), так что если их у нас 10 в ряд, то нам нужно подождать 10 игровых тактов (fps), чтобы начать следующий такт системы. В противном случае сигналы перепутаются и вычисление не будет выполнено.

PC (Program Counter, счётчик программ) счётчик сообщает, в какой части программы мы находимся. Программы считываются из 16-байтной памяти (один байт содержит 8 бит). Счётчик считает до 16 (4 бит) в двоичном формате (0000, 0001, 0010, 0011, 0100, 0101...1111), поэтому каждое из этих вычислений даёт нам адрес регистра, который мы позже можем забрать из памяти и выполнять с ним действия. Также он содержит Jump, сбрасывающий счётчик. Мы можем ввести другое значение, чтобы перейти в конкретное место нашей памяти/кода.

BUS главная точка соединения всех компонентов компьютера. Мы можем передавать в неё/из неё данные. Для этого мы используем сигналы управления, открывающие/закрывающие затворы каждого компонента, чтобы никогда не были открытыми более двух затворов (благодаря этому данные не перемешиваются).

ALU наш калькулятор, выполняющий операции сложения и вычитания (в более сложных ЦП он способен на гораздо большее!). Он мгновенно получает то, что находится в регистрах A и B, а затем выполняет логические операции (операцию мы выбираем декодером команд). Затем эти данные выводятся на шину (bus). Также ALU хранит флаги, которые можно использовать в функциях условных переходов.

Регистры A и B они могут хранить 8-битные числа, которые позже соединяются в ALU. Оба регистра могут передавать и получать данные из шины.

Декодер адреса регистра (Register address/Decoder, RAD) считывает 4-битный адрес из шины и декодирует, какую часть RAM мы должны считать. Например, адрес 1110 содержит значение 0000 0011 (см. изображение).

RAM современные PC обычно имеют примерно 16 ГБ памяти (16 000 000 000 байт). У нас есть всего 16 байт Это оставляет нам место, которого хватит на 16 команд или на меньшее количество команд, благодаря чему мы сможем хранить данные/переменные в других частях памяти. По сути, RAM здесь это 16 разных регистров, как те, которые мы используем в другом месте, просто доступ к ним осуществляется через декодер регистров.

Регистр команд (Instruction register, IR) / декодер (decoder, DC) мы можем помещать данные в регистр команд из BUS, а затем декодировать их, чтобы сообщать, как должна себя вести программа. Используется всего 4 бита (выделены бирюзовым цветом), что даёт нам 16 типов команд, которые можно запрограммировать. Допустим, у нас есть команда OUT, выводящая на печать то, что хранится в регистре A. Она закодирована как 1110, поэтому когда такая команда попадёт в регистр, мы сможем декодировать её и сообщить компьютеру, как действовать.

Счётчик микрокода (Microcode counter, MC) похож на счётчик программ, но находится внутри декодера команд. Даёт нам возможность пошагового выполнения каждой команды.

LCD/Screen на самом деле является регистром, но более сложен, поскольку печатает своё содержимое на LCD-экране (Lamp-Combinator-Display, дисплее из фонарей и комбинаторов).

Распределительная панель (Switch board, SB) эта панель показывает нам, какие функции переключения мы отправляем для управления каждым из компонентов компьютера. На данный момент существует 17 разных переключателей, управляющих различными вещами. Например, если мы хотим считать из BUS в регистр A, или записать в память/регистр команд и т. п. Описанные ниже переключатели можно использовать для ручного управления машиной.

Флаги (Flags, F) регистр для хранения флагов (перенос [T] когда мы превышаем 8-битные значения при сложении, обнуление [O] когда сумма/разность равна 0). Они помогут нам в командах условного перехода.

image

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

CLK наш генератор синхронизации, самое важное в любых вычислениях. Я хотел создать генератор, одновременно имеющий высокий [C = 1] и низкий [C = 0] сигнал.

(1) Это базовый комбинатор констант, подающий сигналы на генератор. Он переходит к (2), где объединены вместе ввод и вывод. Благодаря этой конфигурации с каждым тактом игры (UPS) значение [C] увеличивается на 1. Когда оно достигает [Z], то сбрасывается до 0. То есть Z сообщает нам, сколько игровых тактов необходимо для сброса генератора. Ниже также есть простой делитель на 2, благодаря которому генератор половину времени находится в состоянии высокого сигнала, а половину в состоянии низкого сигнала. Когда C меньше значения [Y] (которое равно половине [Z]), генератор находится в высоком состоянии, в противном случае в низком.

Манипулятор (inserter) (4) используется в качестве вторичного генератора синхронизации на случай, если нам нужно больше контроля над тактами. Если поместить что-нибудь в первый сундук, то произойдёт такт. Если нам нужно 5 тактов, нужно поместить в него пять объектов.

(5) это первый сигнал управления. [H] это сокращение от команды HALT {HLT}. Когда он имеет низкое значение [H = 0], то генератор работает обычным образом, а если высокое, то переходит в ручной режим. Способствуют этому управляющие затворы, они (5a) используются для обычной работы, а когда сигнал [H] не равен 0, то включается ручной режим и выводится [C] (наш CLK).

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

Сигнал [C] перемещается по системе через зелёный провод. Я хотел выделить его на совершенно отдельный провод (например, наша шина BUS находится на красном проводе), чтобы его легко было отслеживать и нельзя было перепутать с другими сигналами.

image

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

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

Вкратце о том, как они работают. При каждом импульсе синхронизации они сбрасывают то, что есть внутри, и сохраняют входящее значение. Если входящих значений нет (все нули), они очищаются в цикле синхронизации. Разумеется, мы не хотим, чтобы они всегда были пустыми, в конце концов, нам ведь нужно хранить в них значения. Поэтому мы используем логику управления, о которой я сейчас расскажу, а чёрной магией создания триггера мы займёмся позже.

Хранящиеся значения (1) отображаются при помощи фонарей. Включенный фонарь означает 1, отключенный 0. Как можно увидеть, сейчас мы храним значение 1110 1001.

Чтобы вывести значение в шину, мы используем логику управления на затворе (2). При низком сигнале [K] этот затвор выводит в основную шину то, что находится внутри регистра.

Почему мы используем его, когда сигнал низкий, а не высокий? Потому что логические затворы выводят всё, что на них подаётся (красный символ *), и в результате в шине окажется сигнал [K], а нам этого не нужно, нам нужны там только [7, 6, 5, 4, 3, 2, 1, 0]. По той же причине нам нужно отфильтровывать сигналы управления при помощи затвора (3), чтобы получать [K] только тогда, когда он нам нужен.

Затвор (4) соединён и с шиной (красный провод), и с сигналами управления (зелёный провод). Как и в предыдущем случае, регистр получает ввод при низком сигнале [A]. Чтобы отфильтровать все остальные сигналы, мы используем логический затвор (4a). По сути, он берёт все вводы с шины и нежелательные сигналы управления, а затем складывает их с комбинатором (4b), вводами которого всегда являются сигналы [7, 6, 0] = 1. Тогда если какой-то из сигналов равен 0, то он выводит каждый из этих сигналов = 1. Всё просто, правда? В таком случае в регистры попадают только те значения из шины, которые нам важны (значения 0 всё равно будут 1, они мигают на один такт, а затем остаются отключенными на протяжении всего высокого цикла CLK).

В такой ситуации, когда [C] становится высоким, затвор (6) выводит сигнал [BLACK], а затвор (6a) сводит к нулю [C]. Но поскольку на сведение к нулю требуется на 1 больше UPS, за такое короткое время затвор (6) выводит сигнал.

Этот сигнал затем переносится на затвор (7), который тоже открывается на короткое время. Затвор (7b) сводит к нулю сигнал [BLACK], чтобы он не хранился в затворе (8), который используется как хранитель нашего сигнала. Это происходит аналогично цепи CLK, потому что и ввод, и вывод соединены вместе. Если ввода нет, то он остаётся неизменным. Если мы ещё раз изменим такт, не вводя новых данных, то затвор (7a) введёт сигнал, инвертированный относительно сохранённого в регистре значения, чтобы очистить его.

Теперь, когда мы знаем, как работают распознавание изменений и регистры, нам известно практически всё.

image

ALU постоянно складывает/вычитает то, что находится в регистрах (A) и (B). Мы управляем только тем, выводить ли его в шину [Z] или изменить режим на вычитание [S].

Как это работает? Чтобы разобраться целиком, рекомендую посмотреть некоторые из видео Бена Итера, потому что объяснение будет в три раза длиннее моей статьи.

Я объясню только, как создать подобный сумматор в Factorio.

Для этого нам потребуются три типа затворов: XOR (1), AND (2) и OR (3). К счастью, их достаточно легко создать. Поскольку мы можем использовать в одной линии несколько сигналов, наш первый затвор XOR и AND может быть упрощён всего до двух, и нам не нужно делать их для всех 8 бит. Благодаря этому мы можем сделать (4) частью цепи и продублировать его для каждого бита.

Вычитание выполняется сигналом [S], инвертирующим сигналы, поступающие из регистра (B).

Также ALU выводит перенос (когда сумма превышает 8 бит), обнуление и хранит его в регистре справа (F на изображении с основным компьютером).

image

LCD/Screen выглядит пугающе, но, честно говоря, изготовить его было проще всего. Нужно лишь время для того, чтобы всё правильно подключить.

Сначала мы создаём регистр, вход которого управляется сигналом [P]. Затем мы умножаем каждый бит на его значение, преобразуя его в десятичную величину, чтобы получить тот же сигнал с десятичным значением (это своего рода читерство в Factorio, однако отсутствие программируемых EEPROM не даёт нам особо развернуться). Для преобразования нам достаточно взять первый бит [0] и умножить его на *1, затем взять второй бит [1] и умножить на *2, третий [2] на * 4, и так далее. В процессе мы выполняем вывод в какое-то произвольное значение для определения получившегося числа (в данном случае это [капля воды]).

LCD включается 9 шагами для чисел (3). Нам нужно просто задать те фонари, соответствующие шагам (1), а затем использовать затворы (2) для вывода значения именно туда, куда нам требуется. Просто нужно не забывать добавлять отдельный комбинатор констант (3) и подключать его только к одному специальному затвору (2). Затем мы просто подключаем друг к другу все фонари и даём им инструкции, на каком шаге они находятся (1).

image

RAM/регистр памяти (RAD) здесь я объясню, как примерно устроена ОЗУ.

Мы уже знаем регистры, использующие импульсы синхронизации для хранения значений. RAM это просто сетка из 16 (в нашем случае) разных регистров (2). Их входы управляются другим регистром (1), хранящим 4 бита [0, 1, 2, 3], который сообщает, на какую ячейку памяти мы указываем. Это реализовано при помощи декодера адреса (3), принцип работы которого похож на LED/Screen. Каждый затвор получает значение от комбинатора констант (в нашем случае 1100 bin = 10 dec), а затем выводит название сигнала соответствующего регистра (в нашем случае [M]), чтобы можно было получить доступ к значению (в нашем случае 00110 0011).

Здесь также стоит упомянуть о программировании памяти вручную. Это можно сделать при помощи сигнала [W], включённого/отключенного при помощи комбинатора констант (4). Другой комбинатор (5) позволяет нам менять адрес, а для ввода значения мы используем ещё один комбинатор (6). В конце мы просто кладём всё в сундук (7), чтобы при синхронизации вручную передавать значения в RAM, не касаясь основного CLK компьютера.

image

Счётчик программ (Program counter, PC) его задача заключается в подсчёте того, на каком шаге программы мы находимся (1). При запуске он имеет значение 0000, этот адрес считывается из RAM и передаётся в регистр команд для интерпретации. После завершения команды мы можем выполнить инкремент счётчика сигналом [X], затем он становится равным 0001, и на следующей итерации этот адрес берётся из памяти, а цикл продолжается.

Разумеется, иногда нам нужно выполнять безусловный или условный переход к другим частям программы. Мы можем делать это при помощи сигнала [J]. Если он низкий (в нашем случае низкий означает активный), то он сбрасывается, считывает из шины адрес, на который должен совершить переход, и сохраняет его в регистре (2). Когда [J] снова становится высоким, через него подаёт сигналы детектор изменений (расположен прямо под 2) на PC.

Сам счётчик работает аналогично CLK, но вместо постоянного отсчитывания тактов он отсчитывает такты при обнаружении изменений в CLK (на самом деле, только когда активны X и CLK). Это можно увидеть прямо на изображении (1).

Затем сигнал можно подать на шину с помощью сигнала управления [C].

image

Распределительная панель (Switch board, SB) настал подходящий момент для объяснения каждого сигнала управления, используемого в программе.

Сигналы разделены на два цвета, зелёные идут налево, красные направо. Каждый сигнал от комбинаторов констант на самом деле передаётся как значения [-1]. То есть когда комбинаторы установлены на * != 0, они могут выводить сигнал 1. Благодаря этому, когда логика управления подаёт сигнал [1], они аннулируются, и мы получаем [0], а во всех случаях нам нужно именно это (подробнее можно прочитать в той части, где я объясняю регистры).

[H] останавливает генератор синхронизации (переключает в ручной режим), высокий сигнал означает, что CLK не переключается.

[Q] адрес RAM, в котором находится регистр, при высоком сигнале регистр адреса RAM сохранит значение из шины в следующем такте CLK.

[Y] вход памяти RAM, при высоком сигнале RAM сохранит значение из шины в следующем такте CLK (по адресу, хранящемуся в регистре адреса).

[R] вывод RAM, при высоком сигнале RAM выводит значение в шину в следующем такте CLK (из адреса, хранящегося в регистре адреса).

[V] ввод регистра команд, при высоком сигнале регистр команд сохраняет значение из шины в следующем такте CLK.

[U] вывод регистра команд, при высоком сигнале регистр команд выводит значение в шину в следующем такте CLK (только 4 последних бита [3, 2, 1, 0]).

[C] вывод счётчика программ, при высоком сигнале счётчик программ выводит значение в шину в следующем такте CLK (только 4 первых бита [7, 6, 5, 4]).

[J] ввод адреса перехода, при высоком сигнале счётчик программ задаст значение из шины в следующем такте CLK (только 4 последних бита [3, 2, 1, 0]).

[X] увеличение значения счётчика команд, при высоком сигнале счётчик программ выполняет инкремент в следующем такте CLK.

[A] ввод регистра A, при высоком сигнале в регистр A сохраняется значение из шины в следующем такте CLK.

[K] вывод регистра A, при высоком сигнале из регистра A выводится значение в шину в следующем такте CLK.

[Z] вывод ALU, при высоком сигнале ALU выводит значение в шину в следующем такте CLK.

[S] вычитание (ALU), при высоком сигнале ALU меняет свой режим со сложения на вычитание.

[B] ввод регистра B, при высоком сигнале в регистр B сохраняется значение из шины в следующем такте CLK.

[L] вывод регистра B, при высоком сигнале из регистра B выводится значение в шину в следующем такте CLK.

[P] ввод регистра LCD/screen, при высоком сигнале в регистр LCD/screen сохраняется значение из шины в следующем такте CLK, и это значение отображается.

[W] ввод регистра флагов, при высоком сигнале регистр флагов сохраняет из ALU перенос (при превышении 8 бит), ноль (когда операция ALU = 0000 0000).

[розовый сигнал] поднят флаг переноса [T]

[бирюзовый сигнал] поднят флаг ноля [O]

Теперь допустим, нам нужно выполнить действие OUT: взять то, что находится в регистре A, и вывести это на LCD/screen (регистр). Чтобы сделать это вручную, нам достаточно включить (отключив комбинатор констант для определённой буквы) сигнал [K] (вывод регистра A -> шина) и сигнал [P] (шина -> ввод регистра lcd/screen), затем выполнить такт CLK.

image

Регистр команд / декодер / счётчик микрокода именно здесь начинается магия. Теперь, когда мы знаем, как вручную управлять компьютером, это поможет нам понять. что нужно делать, чтобы он мог управлять собой сам.

(1) счётчик микрокода досчитает до 8 (число можно уменьшить, если нам не нужно так много), то есть мы можем выполнить 8 различных команд включения/выключения для выполнения действия в одной команде.

(2) команды считываются в регистр из шины, для этого нам нужно включить сигналы [C] (вывод счётчика команд -> шина) и [Q] (шина -> ввод адреса памяти), а затем считать RAM [R] (вывод RAM -> шина) в регистр команд [V] (шина -> регистр команд), а также выполнить инкремент счётчика [X].

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

Когда в регистре что-то есть, мы можем использовать таблицы истинности, похожие на те, которые мы создали для регистра адресов RAM и вывода на LCD/screen.

Значения [D] из регистра команд (он всегда больше 8) и счётчика микрокода (всегда равного или меньшего 8) можно сложить, и при помощи полученного числа мы можем создать логические затворы. Это реализуется затворами (3).

В примере показана команда 0110 XXXX (48 + X в dec, для которой я запрограммировал команду JMP), которая затем прибавляется к шагу 2 счётчика микрокода, что дает 50.

Ещё один пример: команда ADD (0010 XXXX 16 + X в dec); после шагов 0 и 1 микрокод будет на 2, то есть регистры 18-24 можно использовать для другой части кода (в этом случае нам нужны только 18-20, поскольку ADD это процесс из 3 шагов).

(5) Флаги переноса обрабатываются простыми логическими затворами, ввод на них заблокирован, только если на логические затворы не подаётся перенос [T] или ноль [O].

Ниже представлен мой полный список реализованных команд (можете изменять их или добавлять собственные!):

0 NOP 0000 XXXX ничего не делает.

1 LDA X 0001 XXXX загружает значение из адреса X RAM в регистр A.

2 ADD X 0010 XXXX загружает значение из адреса X RAM в регистр B, а затем выводит сложение и помещает его в регистр A.

3 ADD X 0011 XXXX загружает значение из адреса X RAM в регистр B, а затем выводит вычитание и помещает его в регистр A.

4 STA X 0100 XXXX загружает значение из регистра A и сохраняет его в RAM по адресу X.

5 LDI X 0101 XXXX быстро загружает значение из регистра команд (только 4-битное значение) в регистр A.

6 JMP X 0110 XXXX безусловный (происходит всегда) переход к значению X (присваивает PC значение X).

7 JC X 0111 XXXX когда значение переноса [T] равно истине, выполняет переход к значению X (присваивает PC значение X).

8 JO X 1000 XXXX когда перенос нуля [O] равен истине, то переходит к значению X (присваивает PC значение X).

9 OUR X 1001 XXXX выводит на экран значение из адреса X RAM.
.
.
.
14 OUT 1110 XXXX выполняет вывод на экран из регистра A (X не делает ничего).

15 HLT 1111 XXXX останавливает генератор синхронизации (X не делает ничего).

Давайте напишем простую программу и посмотрим, как она работает!

0 LDA 3 загружаем значение в регистр A из адреса памяти 3

1 OUT выводим на экран значение из регистра A.

2 HLT останавливаем CLK, то есть всю машину.

3 42 сохранённое значение

То есть, по сути, эта программа выводит значение, сохранённое по адресу 3 RAM (0011 в двоичном формате).

Давайте преобразуем её в двоичный вид:

0 Адрес: 0000, значение: 0001 0011

1 Адрес: 0001, значение: 1110 0000

2 Адрес: 0010, значение: 1111 0000

3 Адрес: 0011, значение: 0010 1010

То есть, чтобы написать программу, нам нужно выполнить запись в память (W на панели памяти; см. часть с изображением RAM), начиная с адреса 0000, и ввести внутрь значение 0001 0011 (0001 означает команду LDA, где 0011 это X, то есть адрес 3 в памяти).

Затем мы делаем то же самое для других команд.

Не забудьте снова включить для [W] зелёный свет и сохранить halt на счётчике.

Также можно сбросить PC, выполнив переход с помощью J (менять такт CLK не требуется).
Подробнее..

Перевод Параллели между Factorio и проектированием ПО

18.08.2020 08:21:44 | Автор: admin

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

Я играл в две такие игры. Первая это Shenzhen.io. Она похожа на то, чем бы мог заниматься инженер, проектирующий встроенные устройства. Инженер решает головоломки путём писания ассемблерного кода для устройств с низким энергопотреблением. В этой игре здорово то, что её разработчики убрали из неё раздражающие аспекты написания кода и его ввода в эксплуатацию.

  • Требования понятны и на удивление точно соответствуют задаче.
  • Цикл редактирования, отладки и компиляции невероятно быстр. Благодаря этому, а также отличной системе тестирования можно за минуту проверить несколько потенциальных решений.
  • Платформа, от которой зависит код игрока (сама игра) не имеет багов. Не нужно чинить зависимости перед тем, как приступать к написанию собственного кода.

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

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


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

  • Технический долг. Создать ли временный грязный хак или реализовать всё правильно? Ответ всегда один это зависит от условий. Хакинг приближает нас к решению текущих задач, но позже нам придётся вернуть долг. Новички (типа меня!) начинают с соединения различных частей базы конвейерами, пока база не начинает напоминать спагетти, похожее на плохо поддерживаемые кодовые базы. Постепенно мы осваиваем способы укрощения этой сложности, поэтому становится проще обдумывать нашу игровую базу/кодовую базу.
  • Принцип Не повторяйся (Dont Repeat Yourself, DRY). Один из таких способов снижение количества дублируемых элементов. Если нам нужен компонент, который требуется в нескольких местах, стоит ли создать его один раз и использовать повсюду, или копипастить по необходимости? Ответ всегда один это зависит от условий. Проектируя ПО, вы иногда используете библиотеку, а иногда копипастите. Это зависит от сложности компонента пару функций можно скопипастить, а что-то сложное, скорее всего, не стоит. Так и в Factorio определённый компонент (электронные схемы) производился в 4-5 местах. Я заменил их одним централизованным производственным массивом для упрощения структуры фабрики.
  • Масштабирование. В этой игре часто бывает так, что мы создаём производственный массив, а позже обнаруживаем, что необходимо повысить его производительность в 3-5 раз. Когда такое происходит первые несколько раз, для исправленя ситуации требуется полная переделка с нуля. Когда мы становимся умнее, то начинаем проектировать массивы с учётом пространства для масштабирования. Так же происходит и с ПО наши системы необходимо масштабировать под гораздо большее количество пользователей, иногда без каких-либо предупреждений. Мы проектируем наши системы, помня об этом.
  • Пересборка. При пересборке компонента в однопользовательской игре или личном программном проекте мы обычно не беспокоимся о кратковременной приостановке работы этого компонента или целой системы. Однако когда я играл в мультиплеер с друзьями, то пытался сделать так, чтобы компоненты, над которыми я работаю, ничего не ломали. Я создал новую нефтеперерабатывающую систему, перенёс новых потребителей на неё, и уже потом вывел из строя старую. Нулевое время простоя!
  • Отладка для поиска первопричины проблем. Наша фабрика далека от идеала, поэтому при добавлении новых элементов постоянно что-то ломается. Поиск первопричин таких проблем сложная задача, особенно когда их устранение приводит к другим проблемам. Вчерашний пример нам не хватало электричества, поэтому мы добавили больше котлов, но теперь нужно было исправить водопровод. Исчезла проблема с водой, но появились перебои с углём. Всё это отражает реальную жизнь
  • Командная работа. Большинство задач при наличии времени можно решить в одиночку. Но быстрее и интереснее работать с командой, которая тебе нравится. Можно двигаться скорее, разделяя ответственность по членам команды. У нас, среди прочих должностей, был один ответственный за нефть (я), ответственный за поезда, министр обороны. Другие не занимались тонкостями нефтепереработки, им важен только интерфейс, они используют выходные ресурсы и сообщают мне, если что-то ломается. То же самое происходит с крупными программными проектами все не могут знать тонкостей работы всей системы. Поэтому каждый изучает API всех компонентов, и только немногие отвечают за реализацию.
  • Исследование. БОльшую часть времени мы тратили на использование уже имеющегося знания, чтобы оставаться в точке локального максимума выработки. Однако один умный игрок потратил какое-то время на изучение новых техник. В нашей игре угольные электростанции нам не подходили, а от ядерной энергетики мы отказались из-за нехватки урановой руды. Я снова обратился к ней, когда нам отчаянно не хватало энергии и мы производили слишком сильное загрязнение. Оказалось, что при правильной реализации даже небольшого количества руды хватает на питание базы в течение 100 часов. То же самое и с ПО уже готовый стек может работать хорошо, но будет мудро посмотреть, какие ещё есть возможности, и учиться у других. В этом могут помочь и новые члены команды для них всё новое, поэтому они тратят на обучение больше времени, чем вы. Они могут обнаружить нечто, о чём вы не знаете, и использовать это знание для нахождения более высокого максимума. Свежий взгляд на ситуацию это всегда хорошо.
  • Автоматизация. Большинство задач можно выполнять вручную, просто это требует времени. Но если вы делаете что-то много раз, то это нужно автоматизировать. Игра мягко подталкивает вас к этой мысли, требуя автоматизации некоторых аспектов. Позже, когда вы разблокируете строительных роботов, всё становится лучше. Вы можете указать им схему спроектированного вами производственного массива, дать им материалы, и они построят всё за вас. Это напоминает мне AWS CloudFormation и похожие инструменты хоть мы и можем настраивать серверы вручную, быстрее и правильнее будет указать конечное состояние и позволить всё сделать инструменту за вас. Однако если вы разрабатываете ПО, то знаете, что автоматизация не только средство достижения цели, но и сама цель. Мы реализуем её, потому что это делает нас счастливее, даже если её становится слишком много и мы забываем, чем занимались изначально.
  • Тушим пожар. Иногда бывает слишком сложно реализовывать новые возможности, потому что мы отвлекаемся на устранение сигналов тревоги такое в командах разработки ПО происходит очень часто. Обычно в таком случае одному члену команды поручают решать проблемы, а другие продолжают работать над расширением возможностей. В игре происходит то же самое.

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

Мне кажется, что игра в Factorio не сделает вас более умелым проектировщиком ПО. Но если вы проектируете ПО, то игра может показаться вам увлекательной. И наоборот если вы хорошо справляетесь с игрой, то вам определённо стоит попробовать себя в проектировании ПО.

Купить Factorio можно на официальном сайте или в Steam. Если вы хотите попробовать игру перед покупкой, то у неё есть бесплатное демо. (Только один совет не ждите распродажи. У этой игры никогда не было распродаж, а возможно и не будет.)



Благодарю Минеша Патела для вычитывание черновика и предложения по улучшению статьи.

Комментарии можно прочитать на Hacker News и на reddit.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru