Dwarf Fortress — один из тех странных проектов, ставших любимыми в Интернете. Это бесплатная игра, в которой можно быть или авантюристом, или управлять крепостью дворфов в случайно сгенерированном фэнтези-мире. Симуляция очень подробна, каждая новая игра создает множество цивилизаций со своей историей, мифологиями и артефактами.
Она стала знаменитой, и вполне справедливо. Каждый дворф уникален, он имеет эмоциональное состояние, предпочтения в драгоценных камнях и причины для недовольства. И всё это происходит в ASCII-интерфейсе, кажущемся новичкам каким-то обманом, но опытные игроки способны ориентироваться в нём как в бегущем тексте из фильма «Матрица»: они видят в этих символах дворфов, реки, легендарных огромных чудовищ.
Вся игра стала творением одного разработчика — Тарна Адамса с ником Toady One, который работал над Dwarf Fortress с 2002 года. Первые четыре года это был хобби-проект, но с 2006 года он стал его основной работой. Адамс пишет весь код сам, а его брат помогает с дизайном и создаёт истории на основе игры. До недавнего времени он собирал пожертвования, чтобы продолжать работу, но сейчас создаёт версию с пиксельной графикой и переработанным UI, которую можно будет купить в Steam.
Я связался с Тарном Адамсом, чтобы узнать у него о том, как ему удавалось в течение пятнадцати с лишним лет справляться с единой увеличивающейся кодовой базой, сложностями поиска путей и отладкой мёртвых кошек.
Вопрос: Какие языки программирования и технологии вы используете? Каков ваш стек? Менялся ли он за 15-20 лет вашей работы?
Ответ: DF — это некая комбинация из C и C++, но не в каком-то подчиняющемся стандартам формате, а скорее, накопившийся со временем хаос. Я пользовался Microsoft Visual Studio, начиная с MSVC 6, а сейчас работаю в версии Visual Studio Community.
Для решения задач движка я использую OpenGL и SDL. Мы выбрали их, потому что их проще портировать на OSX и Linux, хотя я даже сейчас не смог бы сделать всё это самостоятельно. Не знаю, будь у меня выбор, использовал ли бы я сегодня что-то вроде Unity или Unreal, потому что не знаком ни с тем, ни с другим. Однако поддержка собственного движка — это настоящая боль, особенно теперь, когда я работаю с пиксельной графикой. Для создания звуков я использую FMOD.
Всё это оставалось неизменным на протяжении многих лет развития проекта, за исключением SDL, который был добавлен спустя несколько лет после начала, чтобы можно было создавать порты. Что касается механик игры, то я не пользуюсь большим количеством внешних библиотек, но иногда подбираю библиотеки для генерации случайных чисел — очень давно я добавил Mersenne Twister, а последним дополнением стал SplitMix64, о котором рассказывали в докладе на последнем Roguelike Celebration.
В: Какие сложности возникают при такой длительной разработке одного проекта? Считаете ли вы, что проще делать его самому? Ведь если каждую строку написал ты, его проще поддерживать и изменять?
О: Всё очень быстро забывается! Если посчитать количество символов ";", чтобы приблизительно оценить объём, то получится, что в коде примерно 711 тысяч строк, поэтому я уже просто не могу держать всё в голове. Я пытаюсь давать своим переменным и объектам стандартизированные и легко запоминающиеся имена, и оставляю достаточно много комментариев, чтобы напоминать себе, что происходит, когда мы доходим до конкретного участка кода. Иногда требуется несколько операций поиска, чтобы найти именно тот поток, над которым мне нужно поработать, когда я возвращаюсь к какому-то фрагменту кода, десяток лет остававшемуся неизменным, что бывает достаточно часто. Можно сказать, что большинство изменений происходит в отдельных частях игры, поэтому есть некое активное расплавленное ядро, которое знакомо мне больше всего. Но есть и сильно закостеневшие части кода, которые я не трогал с момента первого релиза игры в 2006 году.
Что касается простоты работы в одиночку, то это идеальный вариант, особенно для меня, не имевшего опыта работы над крупным коллективным проектом! Очевидно, что другие люди выполняют задачи иначе, например, в контексте AAA-игр, и чтобы справиться вовремя, в этой сфере нужно много инженеров. Не могу с уверенностью сказать, что способен вносить изменения быстрее, чем они, потому что раньше не работал в этом контексте, и если нужно внести изменения, передо мной не встаёт никаких бюрократических преград. Я могу просто браться и делать. Но делать приходится в одиночку.
В: Самый крупный рефакторинг/изменение, которое вам приходилось вносить?
О: Было несколько рефакторингов, которые длились месяцами, я переделывал некоторые структуры данных и тому подобное, однако я не считаю, что это можно назвать строго рефакторингом, потому что всегда есть возможности параллельно совершенствовать механики, и это логично делать, пока твои знания кода ещё свежи.
Добавление координаты Z, чтобы механически игра вышла в 3D (оставаясь при этом текстовой), было одним из таких изменений, оказавшимся почти самой сложной задачей за всю мою жизнь. Мне потребовались долгие недели на изучение логики и вызовов функций, в которых использовались X и Y, и на анализ того, как в них встроится ось Z.
Внедрение полиморфизма в систему предметов в конечном итоге оказалось ошибкой, и очень крупной.
В: Почему это было ошибкой?
О: Когда объявляешь класс, являющийся типом предмета, то ты оказываешься привязанным к этой структуре сильнее, чем если бы это были элементы. Возможность использования виртуальных функций и тому подобного удобна, но плата за это слишком высока. Я стал использовать в иерархии предмет «tool», который начал получать различные функции, и теперь может поддерживать всё, от приставной лестницы до улья или ступы (и отдельно песта для неё, ха-ха), и такая система кажется более гибкой, поэтому я хочу, чтобы каждый создаваемый в игре предмет относился к этой иерархии.
У нас много процедурной генерации, и если бы мы хотели, допустим, сгенерировать предмет, который частично действует как один предмет, и частично как другой, то это было бы намного сложнее сделать, если бы мы были ограничены иерархией классов. Добавление таких вещей, как ромбовидных зависимостей и тому подобного в результате запутывает тебя в узлах. При этом существует более чистый способ реализации такой схемы. Если различные компоненты можно просто включать и отключать, то это проще и даёт больше возможностей.
Кажется, некоторые разработчики называют это entity component system, однако те, кто делает сильный упор на оптимизацию, воспринимают её как нечто иное — как систему, в которой ты разбиваешь элементы на отдельные поля. Использование единого объекта с различными распределёнными подобъектами почти всегда хуже сказывается на промахах кэша, однако преимущества для упорядочивания, гибкости и расширяемости такой системы нельзя игнорировать, а различные подполя в предмете «tool» используются не настолько часто, чтобы это превратилось в проблему оптимизации.
В: Столкнулись ли вы с какими-нибудь проблемами при переходе с 32 на 64 бита? Кажется, это один из тех аспектов, которые в своё время были очень важны, но со временем стали достаточно привычны.
О: Вообще никаких проблем! Я с трудом могу припомнить какие-то сложности. К счастью для нас, мы уже и раньше хорошо контролировали байтовые размеры, потому что это важно для сохранения и загрузки миров; с форматом нужно было определиться ещё в начале работы, особенно потому что нам приходится иметь дело с различиями в endian между разными операционками и тому подобным. Поэтому мы избегаем любых хитрых операций с указателями и других вещей, которые могли бы вызвать проблемы. В конечном итоге наш код оказался очень подходящим для перехода на 64 бита только благодаря другим нашим практикам, это было полной случайностью. Основная проблема заключалась в том, чтобы выделить время на внесение изменений, но в конце концов это заняло значительно меньше времени, чем я рассчитывал.
В: Я видел другие игры, похожие на DF, которые тормозили из-за алгоритмов поиска пути. Какой алгоритм вы используете и как обеспечиваете его эффективность?
О: Да, базовый алгоритм — это только часть дела. Мы используем A*, который, разумеется, быстр, но сам по себе недостаточно хорош. Мы не можем воспользоваться некоторыми его инновациями (например, поиском точки перехода), потому что наша карта сильно меняется. Обычно разработчики для упрощения используют решения, добавляющие поверх карты различные крупные структуры, а из-за меняющейся карты их поддержка занимает слишком много времени или слишком трудоёмка. Поэтому мы решили просто следить за соединёнными компонентами, которых можно достичь пешком. Такую систему довольно легко обновлять даже при быстром изменении карты. однако в ней используется заливка. Например, если вода разделяет крепость пополам, то она должна вытечь с одной стороны и присвоить всей половине крепости новый индекс, но после всего этого проблем обычно не бывает. Это позволяет нам вырезать из игры почти все неудачные вызовы A* — нашим агентам достаточно опрашивать числа компонентов, и если они одинаковы, агенты будут знать, что вызов окажется успешным.
Эта система быстра в поддержке, но её недостаток заключается в том, что индексы компонентов хранятся только для ходьбы. Это значит, что летающие существа, например, не имеют знания о глобальном поиске путей, кроме пешеходных. Однако чтобы дать им некоторое преимущество, в боях и некоторых других ситуациях мы используем заливки на короткие расстояния, применяя их обычную логику. Но такое решение для них неидеально.
Не уверен, будем ли мы пробовать другие структуры, чтобы улучшить работу системы. При наших размерах карт все попытки заканчивались провалом, даже когда за них брались сторонние разработчики. Разумеется, если сконцентрировать усилия, решить проблему можно, и я видел, что в некоторых других играх использовались, например, прямоугольные оверлеи и другие решения, которые выглядят многообещающе, но я не знаю, насколько неизменны и крупны их карты.
Самой простой идеей было бы добавление для летающих существ нового индекса, но это сильно повлияет на память и скорость, и поэтому мы не хотим хранить одновременно два индекса; даже один — это уже серьёзная нагрузка. Более специфические оверлеи способны отслеживать свойства прокладывания пути (после чего они прокладывают путь через оверлеи, а не тайлы), но при изменении карты их поддержка сложна и медленна. У нас есть и множество других идей, например, отслеживание лестниц или выполнение ограниченного кэширования путей, и возможно, они могут дать определённые преимущества. Мы определённо находимся на пределе того, что можем на данный момент поддерживать с точки зрения агентов и сложности карты, поэтому чтобы выжать больше, нам необходимо что-то придумать.
В: Кстати, об этом: вы одновременно симулируете множество элементов — как вам удаётся управляться с таким количеством асинхронно (и действительно ли вы используете асинхронность)?
О: Если под асинхронностью понимать многопоточность, то нет, её мы не реализуем, если не считать самого графического отображения. Это многообещающая тема, даже при микропоточности, с которой мне сильно помогло сообщество, но у меня не было времени на глубокое изучение. У меня нет никакого опыта в этом, и такая система сильно подвержена багам.
В: Пробовали ли вы наряду с DF работать над другими проектами/технологиями?
О: Разумеется! Папка побочных проектов, которая мигрировала между компьютерами на протяжении примерно десятка лет, содержит в себе примерно 90 проектов. Некоторые из них длились несколько дней, другие — несколько лет. В основном это другие игры, почти всегда в других жанрах, но есть и несколько вспомогательных проектов для DF, например, прототип генератора мифов. Ничто из этого не близко к релизу, но экспериментировать с ними интересно.
В: Имея около 90 побочных проектов, исследовали ли вы другие языки программирования? Если да, есть ли у вас любимчики?
О: Ха-ха, нет! Мне больше нравится разбираться с архитектурой, чем с технологиями. Однако я уверен, что некоторые вещи сильно бы ускорили реализацию моих архитектур, поэтому мне, вероятно, стоит изучить скриптинг и глубже разбираться с потоками. Другие разработчики любезно предоставили мне в помощь библиотеки и другие вещи, но сложно уделить время работы над побочными проектами для изучения технологий, если моё время для побочных проектов предназначено для расслабления.
В: У вас очень интересные описания изменений в версиях игры. Какой ваш любимый баг и что его вызывало?
О: Наверно, это покажется скучным, но для меня на первом месте баг с пьяными кошками. О нём уже сняли несколько видео. Кошки валяются на полу таверны и кажутся мёртвыми, но оказывается, что они налакались пролитого алкоголя, пока облизывали свои лапы. В коде проглатывания при чистке одно число было не на том месте, и это вызывало у них все симптомы алкогольного отравления (которое мы добавили, когда совершенствовали ядовитых существ.)
Комментарии (15)
HellWalk
16.11.2021 14:49+5Если посчитать количество символов ";", чтобы приблизительно оценить объём, то получится, что в коде примерно 711 тысяч строк
Учитывая, что не все строки заканчиваются на ; - то реально строк еще больше.
Делаю свою браузерную MMORPG с 2016 года - сделано еще очень мало (основная работа съедает почти все время и силы), а когда уже 100 тысяч строк.
Ну а вообще такие долгострои по своему уникальны. Одна только история их разработки заслуживает отдельной интересной истории.
adeshere
16.11.2021 21:02+1Ну а вообще такие долгострои по своему уникальны. Одна только история их разработки заслуживает отдельной интересной истории.
Далеко не всегда. Вот у меня есть аналогичный "долгострой", которому недавно исполнилось 30 лет. Но при паре десятков пользователей (и вдобавок базовый язык - фортран) - кому может быть интересна такая история?
ZhilkinSerg
17.11.2021 18:51Ну тут явно ввиду имелись успешные долгостории.
adeshere
17.11.2021 19:21А что понимать под успешностью? Например, мой - вполне успешен, имхо. У половины пользователей (и у меня самого) это - основной рабочий инструмент. Как у ведьмы - метла, без которой особо не полетаешь. Просто он весьма узконишевый. Ну и к тому же русскоязычный, что снижает количество потенциальных пользователей до минимума. Поди сыщи сейчас настоящую ведьму в наших краях...
Интерес, скорее, будет обусловлен не просто "успешностью", а массовостью применения. Широко используемая технология, развиваемая в течение многих лет, интересна широкому кругу, и наоборот.
FloorZ
18.11.2021 03:55+1Господе, Не будь занудой. Понятно же, что контекст имел ввиду славу проекту. Сарафанное радио распиарило в свое время этот проект так, что о нем даже офисный планктон упоминает, как о каком то древнем Граале игростроя.
ZhilkinSerg
18.11.2021 09:14Да, для узконишевого проекта (как ваш) массовость применения не будет являться составляющей успешности, а вот для игры - будет. Это говорит о том, что наполнения и функционала, добавляемого в течении продолжительного срока проекта хватает для удержания старых и привлечения новых игроков. Кроме того, об игре говорят, пишут, стримят, энциклопедируют, модифицируют, вдохновляются ей в других проектах. Играют в нее, наконец (даже с учетом всего того разнообразия, что предлагалось на игровом рынке за все прошедшие годы).
Expany
17.11.2021 19:16Вы не поверите, например мне.
С интересом почитал бы историю становления Вашего долгостроя. Язык значения для меня особо не имеет.
Так что, жду)
HellWalk
18.11.2021 16:50+2Далеко не всегда. Вот у меня есть аналогичный "долгострой", которому недавно исполнилось 30 лет. Но при паре десятков пользователей (и вдобавок базовый язык - фортран) - кому может быть интересна такая история?
Многим. Я бы послушал. Потому что истории провалов намного более поучительны, чем истории «успешного успеха»
adeshere
19.11.2021 00:10+1С интересом почитал бы историю становления Вашего долгостроя. Язык значения для меня особо не имеет.
Так что, жду)...Многим. Я бы послушал. Потому что истории провалов намного более поучительны, чем истории «успешного успеха»
Хорошо, мне не сложно. Только должен предупредить, что я не программист, а научный сотрудник. Поэтому история у меня будет одновременно типовая и нетипичная, а точнее - офтопик. Типовая - потому что проектов, подобных нашему, в научной среде миллион и маленькая тележка (только большинство из них умирает гораздо быстрее). А офтопик - потому что научные программы обычно пишутся "для себя", что в корне отличает их от обсуждаемых здесь продуктов "для пользователей". Почему я и сомневаюсь, уместен и здесь этот лонгрид. Впрочем, некоторые аналогии с разработкой нормальных программ у нас тоже прослеживаются. Так что пожалуй не поленюсь и все-таки напишу ;-)
Но сначала о том, зачем вообще научные сотрудники пишут программы.
В науке основной результат - это статьи. Цель анализа данных - поиск закономерностей; иногда - оценка параметров. Никого не интересует, как именно ты все это считал. Только вот задачи в науке часто нетиповые, и найти для них готовые программные инструменты получается далеко не всегда. Даже если такие есть, об этом трудно узнать, не попробовав. А чтобы попробовать, надо программу установить и освоить - что очень недешево в плане времени. А вдруг она "не взлетит"? Любой человек боится оказаться у разбитого корыта, когда половина времени, отпущенного на проект, уже истекла. А еще надо будет объясняться с завлабом - на кой черт мы купили эту программу, если она не умеет сделать вот эту вот закорючку (без которой завлаб не признает результат драгоценным). В силу всех этих факторов небольшие программы для научных расчетов часто пишутся самостоятельно, даже если это велосипедный велосипед. (Впрочем, иногда это оправданно, если ехать надо по шпалам, и поэтому колеса нужны не круглые, а квадратные).
Но главное, когда программа "своя", а опыт разработки не очень большой, почти всегда возникает иллюзия, что ее можно сделать быстро и качественно (и со всеми необходимыми финтифлюшками) приблизительно к завтрашнему утру. Только вот законы природы действуют одинаково на всех программистов, даже научных сотрудников. Время идет, а тестирование обнаруживает все новые баги. А когда результат наконец-то посчитан, становится ясно, что изначальные требования были хорошими, но неправильными. Ведь наука - это движение в неизвестность, и путь, который казался прямым, почти обязательно упирается в тупики и завалы. В общем, все хорошо, только надо чуть-чуть переделать логику интерфейса, а также структуры данных и алгоритмы. Буквально совсем немного... а вот эти три функции из пятидесяти можно вообще не менять!
А сроки сдачи отчета горят... чтобы статья успела выйти до декабря, хорошо бы отправить ее в журнал в феврале. Тут уже режется все, что можно. Программа наконец запустилась, но работа с ней напоминает движение по минному полю: шаг влево, шаг вправо в лучшем случае подвесят компьютер, в худшем - рушат институтскую сеть. Единственно правильные опции и ответы, при которых на выходе получается что-то разумное, записаны на бумажке у автора, которая приклеена к монитору. Исчерканная вкривь и вкось по диагонали невразумительными аббревиатурами, она же играет роль ежедневника, а также справочной системы к Программе. О том, чтобы что-то изменить в коде, не может быть и речи (кое-как заработало - так не трогай!). Да и как там что-то изменишь, если смысл написанных накануне в полуобморчном состоянии операторов уже утром становится одной из тайн мироздания даже для автора, а имена переменных, созданных в горячке аврала, формируются по шаблону i1, j13 и k1937?
В общем, нет ничего удивительного, что работа с такой программой заканчивается на следующий день после сдачи отчета. Да, в статье алгоритм может быть описан, как превосходный (и иногда в этом есть доля правды). Считается, что научные сотрудники очень радуются, когда их статьи читают, а методы - применяют. Наивный, но логичный IT-шник даже может подумать, что то же самое относится к их программам. Скажу по секрету, почти у каждого научного сотрудника-программиста в ящике стола лежит молоток, которым можно тихонько ударить по диску с программой, отдавая его коллеге. Это гораздо менее стыдно, чем признаваться, что ты ни черта не смыслишь в собственном коде.
Надеюсь, я ответил на вопрос о провалах? <sarcasm mode: off>
Ну а теперь о нашем проекте.
Его отличие от многих подобных в том, что он создавался на геофизическом полигоне, одной из главных задач которого были наблюдения за процессами в Земле в сейсмоактивном районе. Мы измеряли все, что могли, непрерывно в течение многих лет. Поддержание аппаратуры в рабочем состоянии в полевых условиях стоило огромных усилий. Каждый второй сотрудник занимался получением данных, а каждый первый - их обработкой. Каждая серия наблюдений была уникальной и потому бесценной, а общее отношение к экспериментальным данным можно описать одним словом: "СВЯТОЕ".
Но, это была вторая половина прошлого века, когда почти все приходилось делать вручную. Когда я приехал работать на полигон, компьютеры только-только входили в жизнь. Например, данные автоматического мониторинга двигательной активности животных печатались на рулонной ленте (и это был последний писк техники!), а результаты измерения электрических параметров передавались с отдаленных станций по рации и радист их записывал на бумажке. Затем оператор брал эти ленты и записи и вручную набирал файлы, которые хранились на магнитных дисках СМ-4 (советский аналог PDP-11). Понятно, что когда вводишь тысячи цифр вручную, ошибок не избежать. В лучшем случае можно перепутать или пропустить цифру, в худшем - загнать данные не в тот файл. Ну и работать с разрозненными файлами (каждый в своем формате), содержащими множество опечаток, тоже было не очень удобно. Моей первой задачей была автоматизация ввода и организация базы данных временных рядов. Да, их по-прежнему набирали вручную, но теперь не в редакторе, а в специальной программе, которая предлагала меню со списком рядов, сама подставляла дату и время следующего наблюдения, а также сравнивала вводимые значения с предыдущими и сразу выдавала предупреждение, если обнаруживалось что-нибудь подозрительное. Так начался наш пакет. А поскольку данные - это святое, меня не просто не торопили с релизом, а наоборот, всячески поддерживали в стремлении как-то улучшить программу, сделать интерфейс более удобным для оператора и более защищенным. Плюс я и сам с удовольствием сидел за монитором. С одной стороны, это было захватывающе и ново (диплом я делал на ЕС ЭВМ, где задания запускались в пакетном режиме, а тут впервые увидел настоящий интерактив). С другой - это была затерянная в горах экспедиция, где долгими зимними вечерами делать особо нечего, и самое интересное из возможного - это работа.
Понятно, что в то время мы писали на самом примитивном фортране, почти не имея фундаментальных знаний о программировании. Нас - физиков и геологов - в ВУЗе просто этому не учили. Но когда изо дня в день работаешь с одним и тем же собственным кодом, который все разрастается, к базовым принципам структурного программирования (уже доступного в языке) очень быстро приходишь своим умом. Так, у меня почти сразу появились нормальные (насколько это было возможно) имена переменных, структуры данных в include-файлах (вместо дублирования кода), деление программы на функции небольшого объема (одна процедура - одна задача), и, разумеется, комментарии. Логически связанные группы функций объединялись в изолированные от другого кода библиотеки. Обойтись без операторов IF + GOTO тогда было сложно, но они почти сразу же были поставлены в жесткие рамки. В частности, я следил, чтобы ни один GOTO не перекрывался с другим (то есть между любым GOTO и его целевой меткой не должно быть ни других GOTO, ни их целевых меток), а сам прыжок, как правило, должен быть на одну из ближайших строчек (иначе фрагмент кода выносится в функцию). Это позволило впоследствии довольно легко от них отказаться. (Примерно к началу 2000-х GOTO в программе практически не осталось).
Но главное - нам как-то повезло с самого начала выбрать довольно удачную архитектуру программы. Вместо одного монстра, который одновременно выполняет функции СУБД, обработки рядов и их визуализации, мы с самого начала строили пакет, состоящий из множества отдельных программ, каждая из которых решает одну небольшую задачу, и интерактивной среды, которая позволяет удобно строить конвейер из этих базовых процедур. Это оказалось очень удобным в плане дальнейшего развития пакета и расширения его функций. Позже, когда на рынке появились такие программы статистического анализа временных рядов, как Мезозавр и Эвриста, мы неожиданно обнаружили, что наш пакет - это не просто "на безрыбье рак - рыба", а что он намного мощнее и удобнее этих пакетов - во всяком случае, для наших задач.
Надеюсь, что вам было не скучно прочитать эту историю ;-) Конечно, я мог бы еще рассказать про основные этапы рефакторинга (за 30 лет из было немало), про переход с PDP на IBM PC в DOS, а затем миграцию в Windows, и прочее... но там уже трудно избежать специфических для фортрана подробностей, которые сейчас вряд ли кому-нибудь актуальны ;-)
i__egor
17.11.2021 19:16у меня вот лаба в институте была на 10к строк, это не показатель ничего, в день можно от нескольких десятков до нескольких тысяч нагенерить
RainbowJose
17.11.2021 12:48+1Лайк не читая. А почему нет ссылки на стим версию? Добавьте пожалуйста, пусть люди знают.
Time is subjective
NetNazgul
23.11.2021 16:48Какой ваш любимый баг и что его вызывало?
Мне вот больше всего запомнился баг с гоблинами (и другими существами), варящимися заживо в собственной крови и жировой прослойке, когда обновили анатомию существ и немного не учли теплопроводимость кожи.
yoz
Вроде как скоро DF должна в Стиме выходить с обновленной графикой. Правда не ясно когда таки.
vilgeforce
Вроде об этом уже пару лет назад писали, но пока еще нет :-/