Лекция #33. Плюсы и минусы игровых "орхетектур". [Лектор - dDIMA] (3 стр)
Автор: Арсений Капулкин
[22:17] <dDIMA> Если хорошо подумать, то на самом деле объем передаваемых данных не так уж и велик
[22:17] <dDIMA> Серверная часть (там где Update) ведает коллиженами, перемещениями агентов как точек или физических тел, логикой, скриптами и т.п.
[22:17] <dDIMA> Клиентская часть (Render) - всеми визуальными фиговинами
[22:17] <dDIMA> Синхронизация клиента и сервера происходит в момент окончания каждого ближайшего виртуального фрейма после Vsync
[22:18] <dDIMA> (хотя есть и другие варианты начала цикла рендеринга)
[22:18] <dDIMA> Из очевидных минусов - необходимость грамотной синхронизации,
[22:18] <dDIMA> thread safe и прочих прелестей многопоточного программирования
[22:18] <dDIMA> Вообще тема многопоточного движка сейчас становится очень и очень актуальна :)
[22:19] <dDIMA> Вариант разделения Client-Server хорош, но очевидно бъет систему только на 2 части, что уже мало
[22:19] <dDIMA> Можно предложить нагрузку на систему при помощи организации дополнительных тредов и вынесения туда фоновой загрузки, но это уже напоминает мне танцы с бубном
[22:19] <dDIMA> Мы таким занимались в свое время, когда файлы, sound и т.п. организовывали вокруг себя дополнительные потоки
[22:20] <dDIMA> То есть весь движок был "в душе" singlethread, а на самом деле внутри оно все крутилось по нескольким потокам
[22:20] <dDIMA> Вариант хороший, но для грамотной организации многопоточности (с равномерной загрузкой CPU) не походит
[22:20] <dDIMA> Еще как вариант организации реальной многопоточной системы - это использование параллелизации при событийных вызовах
[22:21] <dDIMA> Т.е. в схеме №2 - с Update Manager можно разнести независимые вызовы по разным тредам
[22:21] <dDIMA> Тут можно много растекаться мыслью, но давайте сделаем еще небольшой перерыв и после этого вернемся к обсуждению GameObjects и непосредственно иерархии игровых классов
[22:21] <dDIMA> Пока что есть текущие вопросы?
[22:23] <dDIMA> тут провокационный вопрос: Какая схема предпочтительна, если фпса стабильно не хватает?
[22:23] <Zeux> если не хватает фпса, надо оптимизировать!
[22:23] <Qiller> Например, знаем что будем работать на low-end железе.
[22:23] <dDIMA> Ну, действительно, надо отпимайзить для low-end
[22:23] <Zeux> либо пытаться time based
[22:23] <Zeux> хорошо, если спасет.
[22:24] <dDIMA> Вообще самым оптимальным будет №1 либо №3
[22:24] <dDIMA> Первый дает максимально возможный, хотя и рваный FPS
[22:24] <dDIMA> Второй - это то же самое, но с обрезкой перформанса, если мы "посмотрели в небо" :)
[22:27] <dDIMA> В пятом варианте: прошло 3 апдейта и 1 рендер
[22:27] <dDIMA> потом еще 2 апдейта и 1 рендер
[22:27] <dDIMA> и так далее
[22:26] <dDIMA> Очевидно, что чтобы все работало хорошо, надо чтобы апдейты занимали < 1/120 :)
[22:27] <dDIMA> вот эта схема мгновенно и оптимизируется
[22:28] <dDIMA> Т.е. вариант часто встречающийся, но будьте начеку и не повзоляйте своим апдейтам вылезать за запланированные замки
[22:28] <_ShaMan_> т.е. в 5-м варианте апдейт прекращает работу по таймауту или идёт все 1/120?
[22:28] <dDIMA> во втором варианте происходит сдвиг отсечки на пятом кадре, так как он длинный
[22:29] <dDIMA> _ShaMan_: если это по таймауту, то надо забыть про фиксированный 1/120
[22:29] <_ShaMan_> эм
[22:29] <dDIMA> тогда получается обычный dt:-)
[22:30] <Zeux> еще вопрос такой - это реально использующиеся игровые циклы, или в реальных проектах делают Render(); Update(); Present(); ?
[22:30] <dDIMA> Скорее тогда уже { Present(); Update(); Render(); }
[22:30] <_ShaMan_> Zeux Update(); Render(); Present()
[22:30] <Zeux> я написал правильно
[22:30] <dDIMA> А, да
[22:31] <dDIMA> Zeux: я не вижу разницы между моим и твоим вариантом
[22:31] <Zeux> рендерим кадр N, потом считаем N+1, потом презентим N
[22:31] <Zeux> а у тебя апдейтим N, рендерим N, презентим N
[22:31] <dDIMA> Как-то более "классический" (привычный) что-ли вариант - это все-таки Update(); Render();
[22:31] <Bacek> ну дело в том что Present это синхронизация с видеодрайвером
[22:31] <Zeux> Present(); Update(); Render(); == Update(); Render(); Present(); :)
[22:31] <Bacek> а рендер только загрузка сцены в память
[22:31] <dDIMA> А вот Present() надо размещать не после Render(), а перед ним
[22:32] <Zeux> гм, почему?
[22:32] <Wraith> может лучше Present(); Render(); Update(); ? тогда видео кушает потихоньку свой пуш-буфер, а мы апдейтимся?
[22:32] <Bacek> помоему надо пресент после рендера :)
[22:32] <Zeux> Wraith, я про Render(); Update(); Present(); и говорю
[22:32] <dDIMA> чтобы пока мы ожидаем конца кадра и считаем следующий, все прососалось в видеокарту :)
[22:32] <Bacek> тк. мы как-бы отрисовываем сцену, а потом выводим на экран
[22:32] <Zeux> это то же самое, что ты написал
[22:32] <dDIMA> Естественно, при условии, что на Update ничего не рендерится
[22:32] <Zeux> такая схема тупо выигрывает в случае GPU bound.
[22:32] <dDIMA> Т.е. Begin делается только в начале рендера
[22:33] <_ShaMan_> эм. а если это первый кадр?
[22:33] <Bacek> я думаю что надо Update(); Render(); Present(); :)
[22:33] <Zeux> _ShaMan_, для первого кадра добавить if :)
[22:33] <dDIMA> Bacek: Update(); Present(); Render();
[22:33] <Zeux> ну или так, да
[22:33] <IronPeter> Дима, а теперь расскажи тот же самый цикл. Но с Бориной травой.
[22:33] <IronPeter> с тонкой синхронизацией SPU ( у него PS3 ) и GPU
[22:33] <dDIMA> Петя, ты содист, да
[22:33] <IronPeter> у тебя главный цикл - он платформозависим?
[22:34] * dDIMA глубоко затянулся
[22:34] <dDIMA> Так скажем, он оптимально платформонезависим, насколько это можно
[22:35] <dDIMA> На самом деле можно рассматривать Present() как часть рендера.
[22:35] <dDIMA> Т.е. это один из его подписчиков
[22:35] <dDIMA> И его можно разместить где угодно.
[22:35] <IronPeter> ну вот ты говорил <dDIMA> в котором апдейт и рендер выполняются независимо и параллельно.
[22:36] <IronPeter> это двойная буферизация?
[22:37] <IronPeter> ну может взять и перемешать рендер и апдейт?
[22:37] <IronPeter> концептуально, в главном цикле.
[22:37] <dDIMA> Петя, ну если говорить про параллельный вариант PS3, то надо последнюю картинку конечно же скорректировать :)
[22:37] <dDIMA> То есть несколько update выполняются в не помню во_сколько_там_потоков
[22:38] <dDIMA> А render параллельно собирает пакеты от обработчиков
[22:38] <dDIMA> Примерно так
[22:37] <Zeux> А, Дима, еще вопрос. Ты будешь говорить про многопоточность? :) Т.е. как жить на 6 ядрах xbox360, или на 16 ядрах на PC, которые уже наверное скоро?
[22:38] <dDIMA> Zeux: я вот как раз и намекнул, что над многопоточностью радо серьезно работать
[22:38] <Zeux> понятно :)
[22:38] <dDIMA> Вариант простой - это 2 треда в Update и Render
[22:39] <dDIMA> Вариант посложнее - это сделасть дополнительные треды и пустить туда что-то
[22:39] <dDIMA> Например, агейскую физику
[22:40] <dDIMA> Причем после дополнительных тредов надо еще постараться, чтобы оно взлетело с равномернойзагрузкой
[22:41] <IronPeter> кстати да, традиционный поток с фоновой загрузкой ресурсов
[22:41] <dDIMA> Ну это совсем просто. Асинхронно и понеслось.
[22:41] <IronPeter> ну он есть в схеме :)?
[22:41] <dDIMA> Посложнее - это процедурно звук поперемешивать, графы достижимости проложить и т.п.
[22:42] <IronPeter> если есть, то вот скажем, нужен ли нам свой менеджер потоков.
[22:42] <dDIMA> В схеме его нет. Т.е. такая вещь легко представляется как singlethread поток, вокруг которого крутятся дополнительные треды
[22:42] <IronPeter> и он не мешает нам создавать пулы потоков, все такое?
[22:42] <dDIMA> не мешает
[22:43] <dDIMA> если сделать перерыв на 10 минут, могу попробовать подрисовать в схеме, что я имел в виду под "не мешает"
[22:43] <dDIMA> Или давай оставим на попозже, после лекции
[22:44] <IronPeter> Дима. как тебе удобно. командуй.
[22:44] <dDIMA> Петя, ну давай я тогда еще чуть-чуть покажу ужасофф, а потом подрисую
[22:45] <dDIMA> Итак, продолжаем
[22:45] <dDIMA> В основном все, что будет сказано ниже, относится к схемам с разделением движок+игра
[22:45] <dDIMA> Если у вас все связано воедино, проектировать систему конечно же существенно проще
[22:45] <dDIMA> Хотя потом замучаетесь ее рефакторить и править функциональность
[22:46] <dDIMA> Я уже ранее говорил, что мне сильно хочется ограничить глубину наследования в С++ цифрой 3-4 :)
[22:46] <dDIMA> Сейчас подробнее поясню, почему
[22:46] <dDIMA> По собственному опыту скажу, что тройка - это одно из наиболее эффективных способов разбиения иерархии классов
[22:47] <dDIMA> Естественно, речь не идет о всяческих смартпоинтерах, autoptr, базовых pure virtual interfaces и прочем
[22:47] <dDIMA> Я имею в виду иерархию крупных игровых классов
[22:47] <dDIMA> Рассмотрим на примере
[22:47] <dDIMA> В Windows имеется замечательный класс (да простят меня слушатели, относящиеся себя к ярым поклонникам С++) HWND
[22:47] * dDIMA надел каску
[22:47] <dDIMA> Базовый класс является основой для конструирования всяческих других классов
[22:48] <dDIMA> Существует куча стандартных классов
[22:48] <dDIMA> И наконец, есть пользовательские классы, которые могут субклассировать как HWND, так и стандартные готовые классы Windows
[22:48] <dDIMA> Четкая и простая схема
15 июня 2007 (Обновление: 13 ноя 2009)