Атмосферика находится в плохом состоянии. Она прошла через несколько рефакторингов, как от оригинального создателя SS14, так и от последующих мейнтейнеров.Были сделаны серьёзные изменения, которые молча сломали ключевые функции атмосферики, что негативно сказалось на игре в целом, до такой степени, что были приняты плохие дизайнерские решения для противодействия предполагаемым эффектам.В настоящее время мейнтейнеры (в основном я, Roomba) предпринимают усилия по полному передокументированию и написанию тестов для атмосферики.
Таким образом, эти рекомендации представляют собой все уроки, которые я извлек до сих пор при исправлении, поддержке и расширении атмосферики.
Общие правила написания кода
Повторные утверждения
Вы должны быть знакомы с Coding Conventions проекта и их основными принципами:- Не копируйте код.
- Не используйте магические строки/числа.
- Полностью комментируйте назначение вашего кода, не просто комментируйте, что он делает.
Все члены должны быть документированы независимо от уровня доступа
Весь код должен быть документирован, дажеprivate члены, даже если это простые вспомогательные методы или поля данных.
Это очень помогает мейнтейнерам при проверке вашего кода и помогает контрибьюторам понять общую картину того, как функционирует атмосферика.
В целом, публичные методы должны иметь надлежащее описание, документацию параметров, предостережения, документацию возвращаемого значения (если применимо), а также пример (если применимо). Распространение такого уровня документации на private члены приветствуется.
Подсистемы должны находиться в своём собственном partial-классе
Подсистемы относятся к процедурам, которые атмосферика выполняет во время обновления. Это большие состояния обработки, такие какRevalidate, TileEqualize, ExcitedGroups, DeltaPressure и т.д.
Большая часть логики, относящейся к этим подсистемам, должна находиться в своём собственном partial-классе в AtmosphereSystem в формате AtmosphereSystem.<Subsystem>.cs.
Любые импорты [Dependency] должны находиться в корневом partial AtmosphereSystem.
Любые const или приватные поля, которые интенсивно используются или управляют конфигурацией указанной подсистемы, должны находиться в partial-классе подсистемы.
Если эти const поля полезны на клиенте, они должны находиться в статическом классе Atmospherics.
Избегайте god-методов, когда возможно
Не используйте ужасающе длинные (например, 350+ строк) методы, выполняющие основную часть логики вашей подсистемы. Разделите ваш метод на отдельные этапы обработки, которые вызывает ваш основной метод. Это может позволить превратить ваш вспомогательный метод в общие методы, которые можно вызывать по всей атмосферике. Например, если вы вывели полезную математическую функцию, которая помогает выравнивать давление между двумяGasMixture, сделайте её публичным методом, который системы могут вызывать в AtmosphereSystem.API.
Математические выкладки должны быть должным образом документированы
Для ваших математических выкладок должна быть предоставлена документация для любых функций, которые вы создали. Документация должна быть встроена в код в виде многострочного комментария и отформатирована в получитаемом виде. Предпочтительна нотация, похожая на LaTeX. Хотя само-документируемые имена переменных и однострочные комментарии на каждом шаге всегда желательны, предпочтителен общий обзор шагов, которые вы предприняли для достижения вашего решения.Новые добавления должны иметь тесты
Будь то новый метод API или новая функция атмосферики, реализация должна иметь тесты, которые должным образом охватывают изменения. Тесты неоценимы для атмосферики, так как большая её часть не протестирована, а косвенные изменения ранее значительно нарушали ключевые механики. Существует доступный вспомогательный класс тестовAtmosTest, который может помочь вам написать тесты для вашей функции.
Улучшения производительности должны быть подтверждены бенчмарком
Было доказано несколько раз (моими собственными попытками улучшения производительности и другими), что некоторые изменения производительности имеют незначительный эффект или не имеют его вовсе. Это прискорбно, так как большинство изменений производительности делали код более сложным и трудным для чтения, будь то через многопоточность, код без ветвлений или векторизованный код. Таким образом, все улучшения производительности должны быть подтверждены бенчмарком. Вы обнаружите, что это фактически необходимо для осмысленной итерации к любой цели производительности, которую вы хотите достичь. Это также очень помогает будущим контрибьюторам опробовать свои собственные изменения производительности и уменьшает количество гипотетических постов для изменений контента, которые могут снизить производительность.Не скрывайте потенциальную числовую нестабильность или шум
При написании кода для атмосферики не игнорируйте потенциальную числовую нестабильность, так как это может скрыть плохие крайние случаи во время тестирования или геймплея. Например, если вы делитеHeatContainer на $ n $ частей, убедитесь, что $ n $ является uint, и используйте вспомогательные методы ArgumentOutOfRangeException, чтобы выбросить исключение, если $ n = 0 $:
- Сделайте Debug assert (
DebugTools.AssertилиDebugTools.AssertNotNull) вашего условия. - Продемонстрируйте с помощью бенчмарка, что пропущенная логика стоит того, чтобы её пропустить.
Большие функции или состояния обработки должны быть максимально конфигурируемыми
При добавлении новой большой функции убедитесь, что конкретное состояние обработки может быть отключено через CVAR. Значенияconst, которые не критичны для производительности или не должны часто меняться, могут быть сделаны CVAR.
Это позволяет форкам включать, отключать или иным образом настраивать определённые этапы атмосферики по своему усмотрению.
Форк не должен вытаскивать всю подсистему, если он хочет её отключить.
Не прикрепляйте крупные функции к существующему состоянию обработки
При добавлении нового поведения для состояния обработки убедитесь, что вы не вкодируете дополнительные проверки и обработку логики. Это делает логику негибкой, потому что, если кто-то захочет добавить ещё одно поведение к функции, ему, вероятно, придётся добавить ещё больше проверок к тем, что вы добавили сами. Например, системаHotspot, которая проверяет, разрешено ли тайлу начинать газовый пожар:
Hotspot должна быть переработана так, чтобы и газовые состояния, и состояния реагентов могли вносить вклад в общий показатель воспламеняемости, который увеличивает температуру тайла.
Это позволит будущим контрибьюторам просто добавлять больше механик, которые вносят вклад в общую воспламеняемость тайла (например, ковёр или деревянная мебель, Диона, стоящая на тайле и т.д.).
Вкодирование большего количества проверок в подсистему вместо создания собственной специализированной подсистемы или обобщения вкладов также означает, что будет сложнее сделать вашу функцию настраиваемой/отключаемой.
Логика атмосферики должна учитывать свой временной бюджет
Атмосферика распределяет свою обработку на несколько тиков, чтобы смягчить удар по времени тикаEntitySystem.
Из-за этого подсистемы должны учитывать, сколько времени они уже потратили с начала тика, и при необходимости уступать обработку следующему тику.
Это обычно делается через секундомер симуляции в AtmosphereSystem._simulationStopwatch.
Состояния обработки проверяют этот секундомер каждые несколько итераций и уступают, если время обработки превысило бюджет для тика:
Переиспользуйте память, когда возможно — избегайте аллокаций в куче для симуляции
При написании кода для атмосферики следите за любыми аллокациями в куче, даже если это что-то вроде перечисленияforeach или List<T>.
Аллокации объектов создают ненужное давление на GC, особенно если эти аллокации находятся в коде симуляции, коде, который выполняется каждый атмосферный тик.
Поэтому переиспользуйте память, такую как массивы, насколько это возможно.
Например, AtmosphereSystem.Monstermos содержит массивы для постановки тайлов в очередь и вычисления распространения давления:
const значения в соответствии с ранее упомянутыми правилами.
Поскольку вы обычно пишете для Content.Server и только для Content.Server, вы можете воспользоваться stackalloc и ArrayPool.
Обратите внимание, что вы всегда должны быть очень осторожны при использовании stackalloc, так как желаемый размер массива для ваших данных может потенциально переполнить стек.
В контекстах многопоточности (например, IParallelRobustJob) предпочтительнее использовать ArrayPool для быстрой аренды массивов.
Не забудьте всегда возвращать их в случае исключения.