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


39

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

Для цього абсолютно чудовий спосіб Unity 3D для того, щоб призупинити гру та змінити активи та код, а потім продовжити та негайно вступити в силу. Моє запитання: чи хтось реалізував подібну систему на ігрових двигунах C ++?

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

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

Але, можливо, це можливо для програмування AI та простих логічних модулів рівня. Це не так, якби я хотів це зробити в будь-якому проекті в короткий (або довгостроковий) період, але мені було цікаво.

Відповіді:


26

Це, безумовно, можливо, хоча і не з типовим кодом C ++; вам потрібно створити бібліотеку у стилі С, яку ви зможете динамічно зв’язувати та перезавантажувати під час виконання. Щоб зробити це можливим, бібліотека повинна містити весь стан у непрозорому покажчику, який може бути наданий бібліотеці після перезавантаження.

Тімоті Фаррар обговорює свій підхід:

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


Я вважаю за краще відповідь Блера, як ти насправді відповідаєш на питання.
Джонатан Дікінсон

15

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

Волохаті, стримуючі речі. Краще використовувати мову сценаріїв для коду, який потребує швидкої ітерації.

Наразі наша теперішня база коду - це поєднання C ++ та Lua. Луа теж не мала частина нашого проекту - це майже розкол 50/50. Ми здійснили перезавантаження Lua під час льоту, щоб ви могли змінити рядок коду, перезавантажити та продовжувати роботу. Насправді ви можете це зробити для того, щоб виправити помилки, які виникають у коді Lua, не перезавантажуючи гру!


3
Ще одним важливим моментом є те, що навіть якщо ви не збираєтесь тримати систему в сценарії, якщо ви очікуєте, що перейдіть на багато ранніх етапів, можливо, все-таки розумно починати в Луа, а потім переходити на C ++ вниз по дорозі, коли вам потрібно додаткові показники та швидкість ітерації сповільнилися. Очевидним недоліком тут є те, що ви закінчуєте перенесення коду, але багато разів правильна логіка є важкою частиною - код майже пише сам, як тільки ви зрозумієте цю частину.
Логан Кінкейд

15

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

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

(Це вийде трохи дотичною, але я обіцяю, що повернеться!)

  • Почніть з даних і почніть з малого: перезавантажтесь на межах ("рівнів" тощо), потім працюйте на шляху до використання функцій ОС, щоб отримувати сповіщення про зміни файлів або просто регулярно проводити опитування .
  • (Для бонусних очок та менших разів завантаження (знову ж, зменшення часу ітерації) розгляньте випічку даних .)
  • Сценарії є даними та дозволяють повторити поведінку. Якщо ви використовуєте мову сценаріїв, тепер у вас є сповіщення / можливість перезавантажувати ці сценарії, інтерпретувати чи компілювати. Ви також можете підключити свого перекладача до ігрової консолі, мережевої розетки тощо для підвищення гнучкості виконання.
  • Код може бути і даними : ваш компілятор може підтримувати накладки , спільні бібліотеки, DLL тощо. Тепер ви можете вибрати "безпечний" час для вивантаження та перезавантаження накладної або DLL, будь то вручну чи автоматично. Інші відповіді тут детально описуються. Зауважте, що деякі варіанти цього можуть зіпсуватись із підтвердженням криптографічного підпису, бітом NX (без виконання) або подібними механізмами захисту.
  • Розглянемо глибоку, вдосконалену систему збереження / завантаження . Якщо ви зможете надійно зберегти і відновити свій стан навіть за умови зміни коду, ви можете вимкнути гру та перезапустити її новою логікою в той самий момент. Простіше сказати, ніж зробити, але це можливо, і помітно простіше і портативніше, ніж тикати пам'ять, щоб змінити інструкції.
  • Залежно від структури та детермінізму вашої гри ви можете робити запис та відтворення . Якщо ця запис трохи перевищує "ігрові команди" (наприклад, подумайте про карту), ви можете змінити весь код візуалізації, який ви хочете, і повторити запис, щоб побачити зміни. Для деяких ігор це настільки ж просто », як запис деяких початкових параметрів (наприклад, випадкове насіння), а потім дії користувача. Для деяких це набагато складніше.
  • Докладіть зусиль, щоб скоротити час компіляції . У поєднанні з вищезгаданими системами збереження / завантаження або запису / відтворення, або навіть із накладками або DLL, це може зменшити обертання більше, ніж будь-яка інша річ.

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

Підтримуючі анекдоти:

На великому ПК RTS (~ 120 осіб, в основному на C ++) існувала неймовірно глибока система економії стану, яка використовувалася щонайменше для трьох цілей:

  • "Неглибоке" збереження подається не на диск, а в двигун CRC, щоб переконатися, що багатокористувацькі ігри залишаються в симуляції блокування по одній CRC кожні 10-30 кадрів; це забезпечило, що ніхто не обманював і піймав десинк-помилок через кілька кадрів
  • Якщо і коли сталася помилка десинхронізації в декількох програвачах, було виконано надто глибоке збереження кожного кадру та знову подається в двигун CRC, але цього разу движок CRC генерував би багато CRC, кожен для менших партій байтів. Таким чином, це може точно сказати вам, яка частина штату почала розходитися в останньому кадрі. Ми виявили неприємну різницю між процесорами AMD та Intel, використовуючи цей режим, "плаваючою платою за замовчуванням".
  • Звичайне збереження глибини може не врятувати, наприклад, точний кадр анімації, в який грав ваш пристрій, але він отримає позицію, стан здоров'я тощо для всіх ваших підрозділів, що дозволяє вам зберігати та відновити в будь-який час під час гри.

З тих пір я використовував детерміновані записи / відтворення на картках C ++ та Lua для DS. Ми підключились до API, який ми розробили для AI (на стороні C ++) і записали всі дії користувача та AI. Цю функціональність ми використовували в грі (щоб забезпечити повтор для гравця), а також для діагностики проблем: коли стався збій чи дивна поведінка, все, що нам потрібно було зробити, - це зберегти файл збереження та відтворити його у налагодженні.

З тих пір я використовував накладки більше декількох разів, і ми поєднали її з нашою системою «автоматично павук цього каталогу та завантажуємо новий вміст у портативну». Все, що нам потрібно зробити, - це залишити cutcene / level / що завгодно і повернутися, і завантажуватимуться не тільки нові дані (спрайти, макет рівня тощо), але й будь-який новий код у накладанні. На жаль, це стає набагато складніше з останніми кишеньковими комп'ютерами завдяки механізмам захисту від копіювання та механізмам антизлому, які спеціально розглядають код Ми все ще робимо це для скриптів lua.

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


6

Ви можете зробити щось на зразок модулів гарячої заміни під час виконання, реалізуючи модулі як динамічні бібліотеки посилань (або спільні бібліотеки в UNIX), а також використовуючи dlopen () та dlsym () для динамічного завантаження функцій з бібліотеки.

Для Windows еквіваленти - LoadLibrary і GetProcAddress.

Це метод C і має деякі підводні камені, використовуючи його в C ++, про це ви можете прочитати тут .


цей посібник - ключ до створення бібліотек з можливістю гарячої заміни, дякую
JqueryToAddNumbers

4

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

  1. Зміна функцій зворотного дзвінка не працюватиме належним чином. E&C працює, додаючи новий фрагмент коду та змінюючи таблицю викликів, щоб вказувати на новий блок. Для зворотних викликів та нічого, що використовує функціональні вказівники, він все ще буде виконувати старі, немодифіковані виклики. Щоб виправити це, ви можете мати функцію зворотного виклику, яка викликає статичну функцію
  2. Змінення файлів заголовків майже ніколи не вийде. Це призначено для зміни фактичних викликів функцій.
  3. Різні мовні конструкції призведуть до того, що вона таємничо не вийде. З мого особистого досвіду, попередньо декларуючи такі речі, як переписки, це часто роблять.

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

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


який цикл компіляції займає 20 хвилин?!? Я вважаю за краще знімати себе
JqueryToAddNumbers

3

Як говорили інші, це важка проблема, що динамічно пов'язує C ++. Але це вирішена проблема - ви, можливо, чули про COM або одне з маркетингових назв, які застосовувалися до нього протягом багатьох років: ActiveX.

COM має трохи невірного імені з точки зору розробника, тому що можна докласти чимало зусиль, щоб реалізувати компоненти C ++, які розкривають їх функціональність, використовуючи його (хоча це спрощено з ATL - бібліотекою шаблонів ActiveX). З точки зору споживача, він має погану назву, оскільки програми, які використовують його, наприклад, для вбудовування таблиці Excel у документ Word або Visio-діаграму в таблицю Excel, як правило, зазнали аварійних ситуацій. І це зводиться до тих самих питань - навіть з усіма рекомендаціями, які пропонує Microsoft, COM / ActiveX / OLE було / важко отримати правильне рішення.

Наголошу, що технологія COM сама по собі не є поганою. Перш за все, DirectX використовує COM-інтерфейси, щоб викрити свою функціональність, і це працює досить добре, як і безліч програм, які вбудовують Internet Explorer за допомогою керування ActiveX. По-друге, це один з найпростіших способів динамічного зв’язку коду С ++ - інтерфейс COM - це по суті лише чистий віртуальний клас. Хоча він має такий IDL, як CORBA, ви не змушені ним користуватися, особливо якщо інтерфейси, які ви визначаєте, використовуються лише у вашому проекті.

Якщо ви не пишете для Windows, не думайте, що COM не варто думати. Mozilla повторно реалізував це у своїй кодовій базі (використовується у браузері Firefox), оскільки їм потрібен спосіб скласти свій C ++-код.


2

Там же реалізація виконання скомпільованих C ++ для ігрової код тут . Крім того, я знаю, що принаймні один фірмовий ігровий движок, який робить те саме. Це хитро, але виконано.

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