Подтвердить что ты не робот

В игровом программировании глобальные переменные плохо?

Я знаю, что моя реакция на глобальные переменные "badd!" но в двух курсах развития игры, которые я взял на моем колледже, глобальные широко использовались, и теперь в учебном руководстве по программированию DirectX 9, которое я использую (www.directxtutorial.com), мне говорят, что глобальные игры в программировании игры в порядке...? Сайт также рекомендует использовать только структуры, если вы можете при программировании игр, чтобы упростить задачу.

Я действительно запутался в этой проблеме, и все исследования, которые я пытался сделать, очень сбивают с толку. Я понимаю, что есть проблемы при использовании глобальных переменных (проблемы с потоками, они делают код более сложным для поддержания, состояние их трудно отслеживать и т.д.), Но также есть стоимость, связанная с не использованием глобальных переменных, мне придется пройти loooot информации, которая может сбивать с толку, и я представляю себе затраты времени, хотя я думаю, что указатели ускорят процесс (это мой первый раз, когда я пишу игру на С++.) В любом случае, я понимаю, что, вероятно, нет "правильных" или "неправильный" ответ здесь, поскольку оба способа работают, но я хочу, чтобы мой код был настолько правильным, насколько я могу, любой вход был бы хорошим, спасибо вам большое!

4b9b3361

Ответ 1

Проблема с играми и глобалами заключается в том, что игры (в настоящее время) имеют резьбу на уровне двигателя. Разработчики игр, использующие движок, используют абстракции двигателя, а не прямое программирование concurrency (IIRC). На многих языках высокого уровня, таких как С++, состояние совместного использования потоков является сложным. Когда многие параллельные процессы имеют общий ресурс, они должны убедиться, что они не наступают друг на друга.

Чтобы обойти это, вы используете concurrency элемент управления, такой как мьютекс и различные блокировки. Это фактически делает синхронные асинхронные критические разделы совместного доступа к коду синхронно для записи. Тема управления concurrency - это слишком много, чтобы полностью объяснить здесь.

Достаточно сказать, что если потоки выполняются с глобальными переменными, это очень сложно отлаживает, поскольку concurrency ошибки - это кошмар (подумайте, "какой поток написал это? Кто держит эту блокировку?" ).

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

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

Говоря все это, я соглашаюсь с точкой "Говорить нет глобальным" , указанной ниже.

Подробнее о сложностях потоков и общего состояния см. ниже:

1 API POSIX Threads - я знаю, что это POSIX, но дает хорошую идею, которая переводится в другой API
2 Википедия Отличный ассортимент статей о механизмах управления concurrency
3 Проблема столового философа (и многие другие)
4 Учебники и статьи ThreadMentor по потоковому использованию
5 Еще одна статья от Intel, но больше о маркетинге.
6 Статья ACM о создании многопоточных игровых движков

Ответ 2

Работали над названиями игр AAA, я могу сказать вам, что глобалы должны быть уничтожены непосредственно перед тем, как они распространится, как рак. Я видел, как они полностью разрушили подсистему ввода-вывода, и ее полностью выкинули, чтобы ее можно было переписать.

Скажите "нет" глобальным. Всегда.

Ответ 3

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

Итак, если вы хотите написать правильный, читаемый и поддерживаемый код, избегайте как можно большего числа глобальных переменных.

Ответ 4

Все ответы до сих пор касаются проблемы с глобальными/потоками, но я добавлю свои 2 цента в обсуждение struct/class (понимается как все публичные атрибуты/частные атрибуты + методы). Предпочтительные структуры над классами на основе того, что проще, находятся в одной и той же мысли о предпочтительности ассемблера над С++ на том основании, что это более простой язык.

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

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

Ответ 5

Прочитав еще немного, что вы опубликовали,

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

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

Ответ 6

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

Я бы добавил к каждому ответу здесь, что, по возможности, следует избегать глобалов, я бы не побоялся использовать все, что вам нужно, из С++, чтобы сделать ваш код понятным и простым в использовании. Если вы столкнулись с проблемой производительности, проконсультируйтесь с этой конкретной проблемой, и я готов поспорить, что большую часть времени вам не нужно будет удалять использование функции С++, но лучше подумайте о своей проблеме. Там могут быть некоторые платформы, которые требуют чистого C, но на самом деле у них нет опыта, даже Gameboy Advance, похоже, очень хорошо справляется с С++.

Ответ 7

Мета-функция здесь - это состояние. От какого состояния зависит какая-либо функция, и насколько она изменяется? Затем рассмотрите, сколько состояний неявно и явно, и перекрестите это с проверкой и изменением.

Если у вас есть функция/метод/что-то, что называется DoStuff(), вы не представляете себе, вне зависимости от того, от чего это зависит, от того, что ему нужно, и что произойдет с общим состоянием. Если это член класса, вы также не знаете, как это состояние объекта будет мутировать. Это плохо.

Контраст к чему-то вроде cosf (2), эта функция понимается как не изменять какое-либо глобальное состояние, и любое состояние, которое оно требует (например, таблицы поиска), скрыто от представления и не влияет на вашу программу в целом, Это функция, которая вычисляет значение, основанное на том, что вы ему даете, и возвращает это значение. Он не изменяет состояние.

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

myObject.hitpoints -= 4;
myObject.UpdateHealth();

и

myObject.TakeDamage(4);

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

extern float value_to_sqrt;
value_to_sqrt = 2.0f;
sqrt();  // reads the global value_to_sqrt
extern float sqrt_value; // has the results of the sqrt.

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

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

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

Ответ 8

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

Ответ 9

Если в классе A вам нужно получить доступ к данным D, вместо того, чтобы устанавливать D global, лучше поместить в ссылку на D.

Ответ 10

Глобалы НЕ являются внутренне плохими. В C, например, они являются частью нормального использования языка... и поскольку С++ построена на C, у них все еще есть место.

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

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

Ответ 11

Две специфические проблемы, с которыми я столкнулся в прошлом:

Сначала:. Если вы пытаетесь отделить, например. (const доступ к большинству игровых состояний) по логической фазе (неконстантный доступ к большинству игровых состояний) по любой причине (развязка скорости рендеринга с логической скоростью, синхронизация состояния игры по сети, запись и воспроизведение игрового процесса в фиксированной точке в кадре и т.д.), глобальные блоки делают это очень трудным для обеспечения этого.

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

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

Если вы попадаете в свое состояние через одну "структуру GlobalTable", которая содержит глобальные таблицы или набор методов на объекте или т.п., ваш литеральный пул имеет тенденцию быть намного меньшим, уменьшая размер .text в вашем исполняемом файле.

В основном это касается архитектур набора команд, которые не могут вставлять цели нагрузки непосредственно в инструкции (см., например, кодировку команд ARM с фиксированной шириной или Thumb версии 1 на процессорах ARM). Даже на современных процессорах я бы сказал, что вы получите немного меньший код.

Это также болит вдвойне, когда ваши кэши команд и данных являются отдельными (опять же, как и на некоторых ARM-процессорах); вы, как правило, загружаете две кегли, где вам может понадобиться только один с единым кешем. Поскольку литеральный пул может считаться данными и не всегда будет начинаться с границы кэширования, то такую ​​же кешью можно загрузить как в i-cache, так и в d-cache.

Это может считаться "микрооптимизацией", но это преобразование, которое относительно легко применяется ко всей базе кода (например, extern struct GlobalTable { /* ... */ } the;, а затем заменить все g_foo на the.foo)... И мы видели код в некоторых случаях уменьшение размера составляет от 5 до 10 процентов с соответствующим повышением производительности благодаря сохранению чистых кешей.