Як можливі детерміновані ігри в умовах недетермінізму з плаваючою комою?


52

Щоб зробити гру такою, як мережа RTS, я бачив ряд відповідей, які дозволяють зробити гру повністю детермінованою; тоді вам потрібно лише перенести дії користувачів один на одного і трохи затримати те, що відображається, щоб "зафіксувати" всі дані до того, як буде зроблено наступний кадр. Тоді такі речі, як позиції підрозділу, стан здоров'я тощо, не потрібно постійно оновлювати в мережі, тому що імітація кожного гравця буде точно однаковою. Я також чув те саме, що пропонувалося робити повтори.

Однак, оскільки обчислення з плаваючою комою не детерміновані між машинами або навіть між різними компіляціями однієї і тієї ж програми на одній машині, чи справді це можливо зробити? Як ми можемо не допустити, щоб цей факт спричинив невеликі розбіжності між гравцями (або повторами), які пульсували протягом гри ?

Я чув, як деякі люди пропонують взагалі уникати чисел з плаваючою комою і використовувати intдля представлення частки дробу, але це не здається мені практичним - що робити, якщо мені потрібно, наприклад, взяти косинус кута? Чи потрібно серйозно переписати цілу математичну бібліотеку?

Зауважте, що мене в основному цікавить C #, який, наскільки я можу сказати, має в цьому плані абсолютно ті самі проблеми, що і C ++.


5
Це не є відповіддю на ваше запитання, але для вирішення проблеми з мережею RTS я б рекомендував періодично синхронізувати позиції блоку та стану здоров’я, щоб виправити будь-який дрейф. Точно, яку інформацію потрібно синхронізувати, залежатиме від гри; ви можете оптимізувати використання пропускної здатності, не намагаючись синхронізувати такі речі, як ефекти стану.
поштовх

@jhocking Тим не менш, це, мабуть, найкраща відповідь.
Джонатан Коннелл

starcraft II infacti точно синхронізується лише кожен раз, я дивився на геймплейну програвання з друзями на кожному комп’ютері пліч-о-пліч, і там, де відмінності (також значущі), особливо з високим відставанням (1 сек). У мене були деякі одиниці, де 6/7 квадрати карт далеко на моєму екрані поважають його власне
GameDeveloper

Відповіді:


15

Чи детерміновані плаваючі точки?

Я багато читав з цього питання кілька років тому, коли хотів написати RTS, використовуючи ту ж архітектуру, що і ви.

Мої висновки щодо апаратних плаваючих точок були:

  • Цей самий кодовий код складання, швидше за все, детермінований, якщо ви обережні з прапорами з плаваючою комою та налаштуваннями компілятора.
  • Був один проект RTS з відкритим кодом, який стверджував, що вони отримують детерміновані компіляції C / C ++ для різних компіляторів за допомогою бібліотеки обгортки. Я не підтвердив цю претензію. (Якщо я добре пригадую, мова йшла про STREFLOPбібліотеку)
  • .Net JIT дозволено досить просто. Зокрема, дозволяється використовувати більш високу точність, ніж вимагає. Крім того, він використовує різні набори інструкцій для x86 та AMD64 (я думаю, що на x86 він використовує x87, AMD64 він використовує деякі інструкції SSE, поведінка яких відрізняється від denorms).
  • Особливо проблематичними є складні інструкції (включаючи тригонометричну функцію, експоненти, логарифми).

Я зробив висновок, що вбудовані типи з плаваючою комою в .net неможливо використовувати детерміновано.

Можливі обхідні шляхи

Тому мені потрібні були обхідні шляхи. Я вважав:

  1. Реалізація FixedPoint32в C #. Незважаючи на те, що це не надто важко (у мене наполовину закінчена реалізація), дуже малий діапазон значень робить його дратівливим у використанні. Ви повинні бути обережними в будь-який час, щоб ні перелити, ні втратити занадто багато точності. Зрештою, я знайшов це не простіше, ніж безпосередньо використовувати цілі числа.
  2. Реалізація FixedPoint64в C #. Мені це було досить важко зробити. Для деяких операцій були б корисні проміжні цілі числа 128 біт. Але .net не пропонує такого типу.
  3. Використовуйте нативний код для математичних операцій, детермінованих на одній платформі. Вводить накладні витрати виклику делегата на кожну математичну операцію. Втрачає здатність запускати крос-платформу.
  4. Використовуйте Decimal. Але це повільно, займає багато пам’яті і легко кидає винятки (поділ на 0, переповнення). Це дуже приємно для фінансового використання, але не підходить для ігор.
  5. Реалізуйте власну 32-бітну плаваючу крапку. Спочатку звучало досить складно. Відсутність властивості BitScanReverse викликає кілька неприємностей при здійсненні цього.

Мій SoftFloat

Натхненний вашим дописом на StackOverflow , я щойно почав впроваджувати 32-бітний тип з плаваючою комою в програмне забезпечення, і результати є багатообіцяючими.

  • Представлення пам’яті є бінарним сумісним з плавцями IEEE, тому я можу переосмислити трансляцію при виведенні їх у графічний код.
  • Він підтримує SubNorms, нескінченності та NaNs.
  • Точні результати не ідентичні результатам IEEE, але це зазвичай не має значення для ігор. У такому коді має значення лише те, що результат однаковий для всіх користувачів, а не те, що він є точним до останньої цифри.
  • Продуктивність гідна. Тривіальний тест показав, що він може зробити приблизно 75MFLOPS порівняно з 220-260MFLOPS з floatдля додавання / множення (Один потік на 2,66 ГГц i3). Якщо у когось є хороші орієнтири з плаваючою комою для .net, будь ласка, надішліть їх мені, оскільки мій поточний тест дуже рудиментарний.
  • Округлення можна вдосконалити. В даний час він скорочується, що приблизно відповідає округленню до нуля.
  • Це все ще дуже неповно. В даний час розподіл, касти і складні математичні операції відсутні.

Якщо хтось хоче внести тести або поліпшити код, просто зв’яжіться зі мною або надішліть запит на тягнення на github. https://github.com/CodesInChaos/SoftFloat

Інші джерела індетермінізму

Є також інші джерела індетермінізму в .net.

  • ітерація над a Dictionary<TKey,TValue>або HashSet<T>повертає елементи у невизначеному порядку.
  • object.GetHashCode() відрізняється від запуску до запуску.
  • Реалізація вбудованого Randomкласу не визначена, використовуйте власний.
  • Багатопоточність з наївною блокуванням призводить до переупорядкування та різних результатів. Будьте дуже обережні, щоб правильно використовувати нитки.
  • Коли WeakReferences втрачає ціль, вона не визначена, тому що GC може працювати в будь-який час.

23

Відповідь на це питання - за посиланням, яке ви опублікували. Зокрема, ви повинні прочитати цитату з газових ігор:

Я працюю в Іграх на базі газу, і я можу сказати вам з перших рук, що математика з плаваючою комою є детермінованою. Вам просто потрібен той самий набір інструкцій і компілятор, і звичайно процесор користувача дотримується стандарту IEEE754, який включає всіх наших ПК та 360 клієнтів. Двигун, який працює на DemiGod, Supreme Commander 1 і 2, покладається на стандарт IEEE754. Не кажучи вже про всі інші програми РТС «однорангові» на ринку.

А потім нижче цього:

Якщо ви зберігаєте повтори як входи контролера, вони не можуть бути відтворені на машинах з різною архітектурою процесора, компіляторами або налаштуваннями оптимізації. У MotoGP це означало, що ми не можемо ділитися збереженими повторами між Xbox та ПК.

Детермінована гра буде детермінованою лише при використанні ідентично складених файлів та запуску в системах, які відповідають стандартам IEEE. Моделювання або повторне синхронізація міжсистемної синхронізації неможливо.


2
Як я вже згадував, мене цікавить насамперед C #; Оскільки JIT робить фактичне компіляцію, я не можу гарантувати, що він "компілюється з тими ж налаштуваннями оптимізації", навіть коли він працює на тому самому комп'ютері!
BlueRaja - Danny Pflughoeft

2
Іноді режим округлення встановлюється по-різному у fpu, у деяких архітектурах fpu має більший за точністю внутрішній реєстр для обчислень ... в той час як IEEE754 не дає можливості вільно враховувати спосіб роботи FP-операцій. t вкажіть, як повинна бути реалізована будь-яка конкретна математична функція, яка може виявити архітектурні відмінності.
Стівен

4
@ 3nixios При такому налаштуванні гра не є детермінованою. Детерміновані можуть лише ігри з фіксованим часовим кроком. Ви вже сперечаєтесь, що щось не є детермінованим, коли ви запускаєте симуляцію на одній машині.
AttackingHobo

4
@ 3nixios: "Я констатував, що навіть за встановленим часовим кроком, нічого не гарантує, що часовий крок завжди буде фіксованим". Ви абсолютно помиляєтесь. Суть фіксованого часового кроку - завжди мати однаковий час дельти для кожного галочки в оновленні
AttackingHobo

3
@ 3nixios Використання фіксованого часового кроку гарантує, що часовий крок буде виправлений, оскільки ми, розробники, не дозволяємо інакше. Ви неправильно трактуєте, як слід компенсувати відставання під час використання фіксованого кроку часу. Кожне оновлення гри має використовувати той самий час оновлення; тому, коли зв'язок однорангового каналу відстає, він все одно повинен обчислювати кожне окреме оновлення, яке воно пропустило.
Keeblebrox

3

Використовуйте арифметику з фіксованою точкою. Або виберіть авторитетний сервер і синхронізуйте його ігровий стан раз у той час - ось що робить MMORTS. (Принаймні, Elements of War працює так. Це написано і на C #.) Таким чином, помилки не мають шансів накопичитись.


Так, я міг би зробити його клієнтом-сервером і боротися з головними болями прогнозування та екстраполяції на стороні клієнта. Це питання стосується однорангових архітектур, які повинні бути простішими ...
BlueRaja - Danny Pflughoeft

Ну, не потрібно робити прогнозування за сценарієм "усі клієнти працюють один і той же код". Роль сервера - бути авторитетом лише для синхронізації.
Nevermind

Сервер / клієнт - це лише один метод створення мережевої гри. Інший популярний метод (особливо для , наприклад, ігор РТС, як C & C або Starcraft) є рівний-рівному, в якому немає ні авторитетного сервера. Щоб це стало можливим, всі розрахунки повинні бути повністю детермінованими та послідовними для всіх клієнтів - звідси і моє запитання.
BlueRaja - Danny Pflughoeft

Ну це не так, як вам абсолютно НЕ МОГАТИ робити гру суворо p2p без серверів / підвищених клієнтів. Якщо вам абсолютно потрібно, то використовуйте фіксовану точку.
Nevermind

3

Редагувати: Посилання на клас фіксованої точки (Остерігайтеся покупця! - Я цього не використовував ...)

Ви завжди можете повернутися до арифметики з фіксованою точкою . Хтось (зробивши rts, не менше) вже зробив роботу з ногами на stackoverflow .

Ви сплатите штрафний збір за продуктивність, але це може бути, а може і не бути проблемою, оскільки .net тут не буде особливо ефективним, оскільки не буде використовувати simd інструкцій. Орієнтир!

NB: Мабуть, хтось із Intel має рішення, яке дозволяє вам використовувати бібліотеку примітивів щодо продуктивності Intel із c #. Це може допомогти векторизувати код з фіксованою точкою для компенсації більш повільної продуктивності.


decimalдобре працювало б і для цього; але це все ще має ті самі проблеми, що і для використання int- як мені зробити триггер з ним?
BlueRaja - Danny Pflughoeft

1
Найпростішим способом було б скласти таблицю пошуку та надіслати її поряд із додатком. Ви можете інтерполювати між значенням, якщо точність є проблемою.
ЛукаN

Крім того, ви можете замінити трійкові дзвінки уявними номерами або кватерніонами (якщо 3D). Це відмінне пояснення
LukeN

Це також може допомогти.
ЛукаN

У компанії, в якій я працював, ми побудували ультразвукову машину з використанням математики з фіксованою точкою, тож вона обов'язково спрацює для вас.
ЛукаN

3

Я працював над низкою великих назв.

Коротка відповідь: Це можливо, якщо ти шалено суворий, але, мабуть, цього не варто. Якщо ви не маєте фіксованої архітектури (читайте: консоль), вона хитра, крихка і має безліч вторинних проблем, таких як пізнє з'єднання.

Якщо ви прочитаєте деякі згаданих статей, ви зауважте, що при встановленні режиму процесора є химерні випадки, наприклад, коли драйвер друку перемикає режим процесора, оскільки він знаходився в одному адресному просторі. У мене був випадок, коли додаток було заблоковано кадром на зовнішньому пристрої, але несправний компонент змусив процесор припиняти роботу через спеку та звітувати по-різному вранці та вдень, що робить його фактично іншою машиною після обіду.

Ці відмінності на платформі полягають у кремнію, а не в програмному забезпеченні, тому для відповіді на ваше запитання також впливає C # Інструкції, такі як злита множина додавання (FMA) проти ADD + MUL, змінюють результат, оскільки він обходить внутрішньо лише один раз, а не двічі. C дає вам більше контролю, щоб змусити процесор робити те, що ви хочете в основному, виключаючи такі операції, як FMA, щоб зробити речі "стандартними" - але ціною швидкості. Властивості, здається, найбільш схильні до відмінностей. В одному проекті мені довелося викопати 150-річну книгу таблиць acos, щоб отримати значення для порівняння, щоб визначити, який процесор був "правильним". Багато процесорів використовують поліноміальні наближення для триггерних функцій, але не завжди з однаковими коефіцієнтами.

Моя рекомендація:

Незалежно від того, підходите ви до цілого блокування або синхронізуєте, керуйте основною механікою гри окремо від презентації. Зробіть гру точною, але не турбуйтеся про точність шару презентації. Також пам’ятайте, що вам не потрібно надсилати всі мережеві дані світу з однаковою швидкістю кадрів. Ви можете розставити пріоритет у своїх повідомленнях. Якщо ваше моделювання відповідає 99,999%, вам не потрібно буде передавати так часто, щоб залишатися в парі. (Профілактика читів убік.)

Є приємна стаття про джерело двигуна, яка пояснює один із способів синхронізації: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking

Пам'ятайте, якщо ви зацікавлені в пізньому приєднанні, вам все одно доведеться кусати кулю і синхронізувати.


1

Зауважте, що мене в основному цікавить C #, який, наскільки я можу сказати, має в цьому плані абсолютно ті самі проблеми, що і C ++.

Так, C # має ті самі проблеми, що і C ++. Але в ньому також є набагато більше.

Наприклад, візьміть це твердження від Shawn Hawgraves:

Якщо ви зберігаєте повтори як входи контролера, вони не можуть бути відтворені на машинах з різною архітектурою процесора, компіляторами або налаштуваннями оптимізації.

Це "досить просто", щоб переконатися, що це відбувається в C ++. Однак у C # це буде набагато складніше впоратися. Це завдяки JIT.

Що ви гадаєте, що трапилося б, якби інтерпретатор запустив ваш інтерпретований код один раз, але тоді JIT зробив би це вдруге? А може, це інтерпретує це двічі на чужій машині, але JIT це після цього?

JIT не є детермінованим, тому що ви дуже мало контролюєте це. Це одна з речей, від яких ви відмовились від використання CLR.

І допоможе вам Бог, якщо одна людина використовує .NET 4.0 для запуску вашої гри, а хтось інший використовує Mono CLR (звичайно використовуючи бібліотеки .NET). Навіть .NET 4.0 проти .NET 5.0 може бути різним. Вам просто потрібно більше контролювати деталі на платформі низького рівня, щоб гарантувати подібні речі.

Ви повинні мати можливість піти з математики з фіксованою точкою. Але саме про це.


Крім математики, що ще може бути різним? Імовірно, "вхідні" дії надсилаються як логічні дії в rts. Наприклад, перемістіть блок "a" в положення "b". Я можу побачити проблему з генерацією випадкових чисел, але це, очевидно, потрібно надіслати і як імітаційний вхід.
ЛукаN

@LukeN: Проблема полягає в тому, що позиція Шона настійно говорить про те, що відмінності компілятора можуть мати реальний вплив на математику з плаваючою комою. Він би знав більше, ніж я; Я просто екстраполюю його попередження у сферу JIT та компіляцію / інтерпретацію C #.
Ніколь Болас

C # має перевантаження оператора , а також має вбудований тип для математики з фіксованою точкою. Однак це має ті самі проблеми, що й використання int- як мені зробити триггер з ним?
BlueRaja - Danny Pflughoeft

1
> Що ви гадаєте, що трапилося б, якби інтерпретатор запустив ваш код> інтерпретував один раз, але тоді JIT зробив би це вдруге? А може, це> інтерпретує це двічі на чужій машині, але JIT це після цього? 1. Код CLR завжди видається перед виконанням. 2. .net використовує IEEE 754 для плавців звичайно. > JIT не є детермінованим, тому що ви дуже мало контролюєте його. Ви робите висновок - досить слабка причина помилкових помилкових тверджень. > І допоможе вам Бог, якщо одна людина використовує .NET 4.0 для запуску вашої гри,> а хтось інший використовує Mono CLR (використовуючи .NET-бібліотеки> курсу). Навіть .NET
Романенков Андрій

@ Романенков: "Ви висновок є досить слабкою причиною помилкових помилкових тверджень". Скажіть, будь ласка, які твердження помилкові та чому, щоб їх можна було виправити.
Нікол Болас

1

Після написання цієї відповіді я зрозумів, що він насправді не відповідає на питання, яке стосувалося конкретно недетермінізму з плаваючою комою. Але, можливо, це корисно для нього, якщо він збирається зробити мережеву гру таким чином.

Поряд із спільним доступом, який ви транслюєте на всіх гравців, може бути дуже корисно створити та транслювати контрольну суму важливих ігрових станів, таких як позиції гравця, стан здоров'я тощо. всі віддалені плеєри синхронізовані. У вас гарантовано виправлені помилки синхронізації (OOS), і це полегшиться - ви будете раніше помічати, що щось пішло не так (що допоможе вам розібратися з кроками відтворення), і ви зможете додати більше реєстрація стану ігор у підозрюваному коді, що дозволяє скористатись дужкою того, що викликає OOS


0

Я думаю, що ідея з блогу, пов'язаного з тим, що все ще потребує періодичної синхронізації, щоб бути життєздатною - я бачив достатньо помилок у мережевих іграх RTS, які не застосовують такого підходу.

Мережі є втратними, повільними, мають затримку і можуть навіть забруднити ваші дані. "Детермінізм з плаваючою комою" (що звучить досить гучно, щоб зробити мене скептичним) - це найменше ваших турбот у дійсності ... особливо якщо ви використовуєте фіксований крок часу. зі змінними етапами часу вам потрібно буде інтерполювати між фіксованими часовими кроками, щоб уникнути також проблем детермінізму. Я думаю, що це зазвичай мається на увазі під недетермінованою поведінкою "плаваючої точки" - саме такі мінливі часові кроки викликають розбіжність інтеграцій - нічого спільного з математичними бібліотеками або функціями низького рівня.

Синхронізація є головним.


-2

Ви робите їх детермінованими. Для прекрасного прикладу див. Презентацію GDC Dungeon Siege про те, як вони зробили локальні мережі у світі бездоганніми.

Пам'ятайте також, що детермінізм застосовується і до "випадкових" подій!


1
Це те, що ОП хоче знати, як робити, з плаваючою точкою.
Качка комуніста
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.