Истребители багов: дефектная страховка корабля

Истребители багов: дефектная страховка корабля

Истребители багов: дефектная страховка корабля

Выпуск от 23 ноября 2017г.

На этой неделе главный "истребитель кружек" Марк Эйбент пребывает в отпуске, поэтому его заменяет младший геймплейный инженер Спенсер Джонсон. Он разберется с багом со страховкой, из-за которого возмещенные корабли появляются на посадочной площадке без некоторых важных элементов конструкции, вроде дверей, турелей, кресел и т.п. Все как в реальной жизни.

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

Мы начинаем у терминала Port Olisar, запрашиваем страховое возмещение для Cutlass Black и ускоряем процесс восстановления корабля. 8 секунд – это, возможно, несколько меньше, чем будет в релизной версии. По окончании обратного отсчета мы вызываем восстановленный корабль, и система сообщает, что он появился на площадке A07. Для экономии времени у Спенсера есть режим полета с возможностью проходить сквозь стены. И вы сразу же можете заметить что не так с кораблем: у него нет дверей, нет посадочных шасси, внутри нет сидений, а сверху нет турели. Эти мошенники из страховой компании взяли наши деньги и, фактически, предоставили нам болванку вместо корабля. Он не работает, на нем нельзя летать. Не лучшая игровая функция.

Итак, что же пошло не так, и почему подобное вообще произошло? Позвольте объяснить. Поставим игру на паузу и погрузимся в код. Посмотрим на функцию под названием GeneratePersistentItems (сгенерировать постоянные предметы). Она находится в куске кода, называемом Entitlement Processor – обработчике предоставления прав. Он является очень важной частью основы нашего кода. Скажем, вы купили корабль на сайте. Когда вы впервые заходите в игру, нам нужно взять информацию о корабле, которым вы владеете, и, по сути, перенести ее в игровые данные. Там этот корабль заносится в базу данных игрока, чтобы игра знала, что вы им владеете. За это отвечает очень мудреный фрагмент кода. Тут много всего происходит, чтобы убедиться, что все предметы будут правильно отсортированы, и игроки получат права на владение своим имуществом. И для системы страховки мы собираемся использовать некоторую часть уже присутствующей здесь функциональности. Когда вы взрываете корабль и обращаетесь за страховым возмещением, вы получаете новый. Для этого используется тот же код, но мы внесем в него некоторые модификации, поскольку он не был изначально предназначен для решения таких задач. Это продуманное решение, и мы пытались действовать умело, но, как видите, создали множество багов.

Давайте немного поговорим об этой функции и способе ее использования. GeneratePersistentItems – это рекурсивная функция. Если вы не знакомы с программированием, то вот это окно OBS наглядно объяснит, что такое рекурсия:

Истребители багов: дефектная страховка корабля

Рекурсия – это когда вы видите что-то, что содержит само себя. В программировании рекурсивной называется функция, которая вызывает саму себя снова и снова. Это очень полезная технология, и именно так работает GeneratePersistentItems. Когда у вас есть предмет, у вас есть корневой объект и дерево дочерних объектов. Вот изображение, которое поможет вам это понять. (Оно также дает понять, почему Спенсер с его навыками MSPaint не сидит в другой части здания в отделе художников.)

Истребители багов: дефектная страховка корабля

Итак, это дерево предметных портов для Cutlass Black. Возможно, вы ожидаете увидеть код, но мы перейдем к нему чуть позже. Корневым объектом здесь является корабль, у которого нет дверей и прочих элементов. Это просто базовая модель. И вы видите, что к его предметным портам прикреплены сиденья, турель, двери. В свою очередь, у турели в качестве дочерних элементов выступают прикрепленные к ней пушки, а у кресла дочерним объектом является приборная панель. Такая древовидная структура демонстрирует, из чего состоят наши корабли. Она очень важна, потому что обработчик предоставления прав начинает работать с самого верха и сначала дает вам корабль, а затем уже все дочерние объекты корабля.

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

Истребители багов: дефектная страховка корабля

В программировании мы по многим причинам начинаем с 0, а не 1. 0 – это родитель, а 1,2 и т.д. – дети. Обработчик предоставления прав как раз и занимается присваиванием идентификаторов. Мы храним эти ID в переменной itemGEID. Каждый раз, когда мы рекурсивно вызываем функцию, мы добавляем к этой переменной единицу. По сути, такой подход используется для присвоения новых ID новым сущностям. То есть когда вы создаете сущность и ее дочерние объекты – вы добавляете единицу. Но когда мы воспользовались этой функцией для страховки, оказалась, что для наших задач такой подход не работал. Дело в том, что мы храним несколько иное число в этой переменной. Вместо ID сущности мы храним там значение смещения в списке. Проблема в том, что мы отправляли значения дочерних элементов, но в действительности нам нужен был их родительский объект. На последней картинке изображены числа, которые мы предполагали получить.

Истребители багов: дефектная страховка корабля

Раньше мы заносили в переменную ID самого элемента, но теперь мы фактически говорим: "У моего родителя такой же номер, как и у меня". Так что функция предоставит вам все элементы корабля, но не будет знать, как соединить их обратно. Она создаст лишь корневой объект, а все дочерние объекты окажутся в вашей постоянной базе данных, но не будут иметь связи с кораблем. Оранжевые номера на рисунке представляют родителя, которому принадлежат дочерние элементы. Мы начнем с корня. Там стоит 0, потому что у него нет родителя. У его дочерних элементов синим цветом отмечены их собственные ID, а оранжевым – ID родителя, т.е. 0. Та же логика используется и для объектов ниже по дереву. Это критически важно. Этими номерами мы воспользуемся позднее, чтобы отметить положение родителя в списке.

Давайте посмотрим на код, который вносит изменения в работу функции. Его не слишком много, но зачастую даже очень сложные баги удается решить лишь небольшими фрагментами кода. Здесь есть один блок, который проверяет, равняется ли переменная entityParentIndex минус единице. Но начать, пожалуй, следует с небольшой модификации функции. Тут целая куча аргументов, но мы добавим еще один – entityListParentIndex (индекс родителя списка сущностей) и сделаем его равным -1. Также поместим его в исходный файл.

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

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

Ниже у нас есть еще один небольшой фрагмент кода с несколько причудливой строчкой. Здесь мы создадим событие newParentIndexInEntityList (новый индекс родителя в списке сущностей). Выглядит немного странно. Если кратко, здесь мы следим за изменением индекса родителя. И если указатель здесь не является NULL (неопределенным), тогда мы проверяем условия. Если оба условия верны, мы берем число, за которым следили через overrideGEID. Затем только что созданное целое число передается в качестве значения нового аргумента newParentIndexInEntityList в функцию. Это новое значение, и вот почему рекурсия это круто. Мы создали это значение у родителя и распространили его по всем дочерним элементам. Рекурсия позволяет вносить подобные изменения очень быстро.

Теперь у нас есть все необходимое для компиляции и нового тестирования. Быть может в этот раз довольно неплохая страховая компания позаботится о нас. Я понимаю, что мы пытаемся передать реалистичность в игре, но все же страховая компания должна предоставлять вам функционирующий корабль. Мы вернулись в игру, и давайте попытаемся еще раз. Заявим страховое возмещение для Cutlass Black, заплатим пошлину за ускорение процесса восстановления и спустя некоторое время отправимся на посадочную площадку. В этот раз у корабля есть двери, равно как и турель, двигатели, кресла и прочие детали. Теперь он выглядит как настоящий корабль.

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

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

Вот и все. Надеюсь, вам понравился этот выпуск с меньшим вниманием к коду, но с картинками и всем прочим описанием проблемы.

Перевод: H_Rush

Обсудить на форуме star-citizen.ru

H_Rush administrator