1) В онлайн-игре каждый тик симуляции меняет игровую ситуацию. Как правило, частота передачи пакетов игрового состояния равняется частоте тиков симуляции. Так что новые данные есть всегда.
2) TCP пакет на нормальном соединении может в любой момент быть дропнут шейпером провайдера, когда канал полностью загружен. Это обычное и регулярное явление, которое вы даже не замечаете, пока не начинаете использовать сверх-интерактивные приложения (а именно такими являются сетевые многопользовательские игры) поверх TCP. Полностью загрузите свой канал и попингуйте ya.ru, чтобы убедиться в этом.
Помимо этого пакет может быть дропнут еще в куче мест пока идет от сервера до вас. Это, можно сказать, нормальное явление для TCP, который по определению не гарантирует доставку пакета с первой попытки.
3) Все верно, TCP решает все транспортные проблемы. Но что значит «очень быстро»? Как я показал, TCP решает проблему как минимум за время пинга. Если этот пакет несет в себе часть картинги в браузере, то это не критично, но даже в этом случае это не очень быстро, а достаточно быстро. Если же это кадр тика симуляции, то увеличение срока его доставки минимум в полтора раза — это очень медленно.
Представим игру, в которой сервер раз в 20 секунд отправляет клиенту по TCP координаты игрока (интервал в 50 миллисекунд). Допустим, что средний пинг конкретного соединения между клиентом и сервером равен 100 миллисекундам, чего более чем достаточно для комфортной игры. Но вдруг один из пакетов дропнулся. В этом случае сервер подождет некоторое время SRTT(Smoothed Round Trip Time), которое равно примерно среднему пингу, т.е. 100 миллисекунд и потом отправит этот пакет повторно, который будет идти до клиента еще 50 миллисекунд.
При этом все остальные пакеты (в данном случае 150/50=3 штуки) были доставлены вовремя, но «протухли» в буфере клиента, поскольку перестали быть актуальными. Вот такая ситуация и называется блокировкой начала очереди.
В случае с более высоким пингом количество мертвого груза пакетов на стороне клиента увеличивается еще больше. Также не стоит забывать, что в случае повторных дропов этого пакета, сервер будет ждать каждый раз с экспоненциально возрастающим значением SRTT.
А в случае передачи по UDP сервер ничего не передает повторно, но и клиент ничего не ждет. И в данной ситуации фриз будет всего 50 миллисекунд вместо 150.
Совершенно согласен. Если проблема в неполучении данных из-за сети, то предложенное решение ее все равно не решит. Если же сетевых проблем между серверами нет, то проблема HoLB решается до безобразия просто — сначала прочитай пришедшие к тебе данные и только потом их обрабатывай. И я уверен что многие тысячи подобных кейсов решены именно таким способом без велокрафтинга. Единственный случай, при котором такой подход не будет работать — это когда объем передаваемого сообщения превышает объем оперативной памяти ноды, но что-то мне подсказывает, что у автора доклада, который одной нодой по его словам обрабатывает миллионы одновременных подключений, не этот случай.
Тут проблема в том, что между клиентом и сервером целый интернет. Сколько будет идти каждый из пакетов — неизвестно. Игрок нажимает вперед, клиент рисует что персонаж сделал шаг вперед, пакет теряется (идет слишком долго), на сервере персонаж стоит там где стоял. Чтобы подтянуть клиентское состояние до серверного нужно сделать шаг назад. А это очень не круто.
Я потому и задал изначально вопрос, как это сделано в данной игре.
Потому что есть очень простой способ избежать вообще всех проблем — на клиенте заранее ни чего не делать. Отправил серверу сообщение, что нажата кнопка вперед, сервер это обработал, на следующий тик оправил уже обновленное состояние, клиент его получит и только теперь начал отображать.
Но тут другая проблема — латенси и как следствие менее отзывчивое управление.
И вот до тех пор пока я не пойму, как сделать приличную синхронизацию, я буду использовать этот тупой способ через увеличение латенси.
Но во многих играх эту проблему как-то решают. В том же ВоВе, например, управление отзывчивое и очевидно, что клиент ничего не ждет и сразу отрисовывает события от контролов.
К сожалению, в многопользовательских играх категорически нельзя перекладывать работу по просчету мира на клиентов — иначе будет читерство 80 уровня )) Во всех без исключения ММО состояние просчитывается сервером.
Серверу не отправляется ничего, кроме статусов нажатых клавиш. Поэтому и привязка к тикам: каждый тик сервер получает от клиента статусы клавиш и апдейтит по ним состояние. Клиент сам ничего не считает, он только отображает то, что к нему пришло от сервера (за исключением персонажа). Когда клиент получает от сервера одно состояние, а сам находится в другом — это коллизия. Способ исправления такой коллизии лежит на поверхности — просто подгоняем состояние на клиенте к тому, что приходит от сервера. Проблема тут в плавности такой подгонки, которую не всегда можно незаметно совершить.
Исправить на следующем тике или на следующих нескольких тиках — это вообще не проблема. Проблема сделать это незаметно для игрока. Пример: игрок стоит на месте и нажимает поворот направо. нажимает в течении 5 тиков и соответственно, клиент его сразу и поворачивает. Но 1 пакет до сервера не доходит, сервер рассчитывает поворот на 4 тика и в итоге при синхронизации клиент начнет крутить игрока в противоположную сторону. А это, на мой взгляд, полный фэйл. Или я чего-то недопонимаю? Как можно исправить такое разногласие незаметно для игрока?
Тогда сразу же следующий вопрос: в случае коллизий позволительно ли подправлять стейт на сервере в разумных пределах, либо править стейт можно исключительно на клиенте? Не всегда можно обмануть игрока. Если расхождения в положении можно более-менее незаметно подправить в процессе движения, то, например, в случае рассинхронизации направления движения (вектор движения строго равен вектору взгляда) нельзя игрока незаметно повернуть, потому что если он будет нажимать вперед, а идти вперед и попутно разворачиваться, это игроку будет очень неприятно. И ждать момента когда игрок начнет крутиться тоже нельзя, потому что до поворота он может пройти достаточно много и, вследствие рассинхронизации, коллизия положения еще больше усугубится.
Тоже пилю игрушку похожую на www.realmofthemadgod.com.
И у меня к Вам такой вопрос. На клиенте после получения события от контрола вы просто отсылаете его на сервер и изменения начнут отображаться только после получения сообщения об изменении состояния от сервера? Или сразу же применяете действие на клиенте одновременно с отправкой сообщения на сервер и потом как-то синхронизируйте клиентский стейт с актуальным серверным?
Очень приличная палитра. Жаль, что не вы создавали палитру для xterm. В то время не заморачивались и тупо порезали RGB-пространство на равномерные куски. В итоге номинально там 216 цветов, но половина из них сливается между собой и практически полностью отсутствуют темные тона. В консоле цвета — это боль…
При необходимости на Go легко реализуется поведение, подобное try/catch, при чем без лишнего оверхеда по сравнению с теми языками, где это выделено в отдельную сущность. Но, по причине отсутствия явной поддержки исключений в языке, в Go это реализуется в более многословном виде без синтаксического сиропчика. Но в случае уместности такого подхода это вполне идиоматично и именно это и нужно делать и именно это и делается, в том числе в стандартной библиотеке языка.
Но как показывает многолетняя практика разных языков программирования, наличие инструкций try/catch провоцирует использование этого инструмента везде и всегда, тогда как на самом деле такой подход к обработке ошибок оправдан лишь в небольшом количестве случаев. Именно поэтому эта функциональность выпилена из языка с корнем. Это компромисс, который заставляет иногда реализовывать try/catch в ручную ради искоренения тотального засилья трай-блоков во всем коде.
Это сделано умышленно и это ни когда не поменяется. И эта особенность не преподносится как идеальное решение, это не преподносится как эталон для подражания, это не преподносится как ноу-хау. Это сделано из соображений циничного прагматизма. И как у любого компромисса, эта особенность языка имеет и слабые и сильные стороны, но в целом этот подход приводит к более качественному и более поддерживаемому коду.
В Go УЖЕ есть универсальная встроенная функция манипулирования слайсами, которая работает с любыми типами и с максимальной скоростью — append. И не нужно ничего изобретать, все уже изобретено и реализовано. Если вам не нравится синтаксис — это ни как не проблемы языка. Более того, так и задумано. Неужели вы считаете, что авторы оказались не способны привнести в синтаксис сахарку, если бы посчитали это необходимым? Я вас уверяю, смогли бы. Тот факт, что эта функция реализована именно таким образом говорит о том, что авторы языка считают именно такой синтаксис наиболее подходящим и я лично с ними в этом солидарен. Этот синтаксис показывает программисту, что операция которую он совершает, не простое присваивание, а ресурсоемкая задача. Нравится вам это или нет, но этот язык именно такой. Если он вам не подходит — не пишите на нем. Но не надо говорить, что язык плох из-за того, что он для вас недостаточно подслащен. Расслабьтесь и наслаждайтесь жизнью в сторонке от Go.
Да и тут речь даже не в том, какой способ лучше. Речь о том, что необходимость таких изощренных манипуляций по определению не может быть простой и не является типичным примером использования. В случае необходимости конечно придется повозиться, но это жизнь, так сказать. Но в 99% случаев при правильном подходе нет необходимости вставлять элементы в середину слайса и в этих 99% случаев слайсы в Пщ очень удобны и лаконичны.
То есть возможность упереться в память при увеличении размера слайса вас не заботит (а это хоть и временное, но двукратное увеличение!) и вы не видите в этом проблемы, а вот пара лишних указателей (или даже один — списки бывают еще и односвязными, что в случае очереди кажется более оптимальным) — это большая проблема?
Да все понятно уже. Ваша бизнес логика давно переросла функционал слайсов, но вместо того чтобы это осознать и использовать другую структуру данных (связный список тут явно напрашивается) вы продолжаете есть кактус и утверждать, что использование слайсов для этой задачи оправданно и типично.
Тогда в случае плохого коннекта на TCP время доставки будет выше, чем на UDP.
В случае идеального коннекта ни какой разницы не будет.
2) TCP пакет на нормальном соединении может в любой момент быть дропнут шейпером провайдера, когда канал полностью загружен. Это обычное и регулярное явление, которое вы даже не замечаете, пока не начинаете использовать сверх-интерактивные приложения (а именно такими являются сетевые многопользовательские игры) поверх TCP. Полностью загрузите свой канал и попингуйте ya.ru, чтобы убедиться в этом.
Помимо этого пакет может быть дропнут еще в куче мест пока идет от сервера до вас. Это, можно сказать, нормальное явление для TCP, который по определению не гарантирует доставку пакета с первой попытки.
3) Все верно, TCP решает все транспортные проблемы. Но что значит «очень быстро»? Как я показал, TCP решает проблему как минимум за время пинга. Если этот пакет несет в себе часть картинги в браузере, то это не критично, но даже в этом случае это не очень быстро, а достаточно быстро. Если же это кадр тика симуляции, то увеличение срока его доставки минимум в полтора раза — это очень медленно.
При этом все остальные пакеты (в данном случае 150/50=3 штуки) были доставлены вовремя, но «протухли» в буфере клиента, поскольку перестали быть актуальными. Вот такая ситуация и называется блокировкой начала очереди.
В случае с более высоким пингом количество мертвого груза пакетов на стороне клиента увеличивается еще больше. Также не стоит забывать, что в случае повторных дропов этого пакета, сервер будет ждать каждый раз с экспоненциально возрастающим значением SRTT.
А в случае передачи по UDP сервер ничего не передает повторно, но и клиент ничего не ждет. И в данной ситуации фриз будет всего 50 миллисекунд вместо 150.
Я потому и задал изначально вопрос, как это сделано в данной игре.
Потому что есть очень простой способ избежать вообще всех проблем — на клиенте заранее ни чего не делать. Отправил серверу сообщение, что нажата кнопка вперед, сервер это обработал, на следующий тик оправил уже обновленное состояние, клиент его получит и только теперь начал отображать.
Но тут другая проблема — латенси и как следствие менее отзывчивое управление.
И вот до тех пор пока я не пойму, как сделать приличную синхронизацию, я буду использовать этот тупой способ через увеличение латенси.
Но во многих играх эту проблему как-то решают. В том же ВоВе, например, управление отзывчивое и очевидно, что клиент ничего не ждет и сразу отрисовывает события от контролов.
И у меня к Вам такой вопрос. На клиенте после получения события от контрола вы просто отсылаете его на сервер и изменения начнут отображаться только после получения сообщения об изменении состояния от сервера? Или сразу же применяете действие на клиенте одновременно с отправкой сообщения на сервер и потом как-то синхронизируйте клиентский стейт с актуальным серверным?
Но как показывает многолетняя практика разных языков программирования, наличие инструкций try/catch провоцирует использование этого инструмента везде и всегда, тогда как на самом деле такой подход к обработке ошибок оправдан лишь в небольшом количестве случаев. Именно поэтому эта функциональность выпилена из языка с корнем. Это компромисс, который заставляет иногда реализовывать try/catch в ручную ради искоренения тотального засилья трай-блоков во всем коде.
Это сделано умышленно и это ни когда не поменяется. И эта особенность не преподносится как идеальное решение, это не преподносится как эталон для подражания, это не преподносится как ноу-хау. Это сделано из соображений циничного прагматизма. И как у любого компромисса, эта особенность языка имеет и слабые и сильные стороны, но в целом этот подход приводит к более качественному и более поддерживаемому коду.