Bagaimana seseorang bisa mengimplementasikan modul C ++ hot-swappable?


39

Waktu iterasi yang cepat adalah kunci untuk mengembangkan game, jauh lebih daripada grafis yang bagus dan mesin dengan truk penuh fitur menurut saya. Tidak heran banyak pengembang kecil memilih bahasa scripting.

Cara Unity 3D untuk dapat menjeda permainan dan memodifikasi aset DAN kode, kemudian melanjutkan dan membuat perubahan segera berlaku sangat bagus untuk ini. Pertanyaan saya adalah, adakah yang menerapkan sistem serupa pada mesin game C ++?

Saya telah mendengar bahwa beberapa mesin kelas atas benar-benar bagus, tetapi saya lebih tertarik untuk mencari tahu apakah ada cara untuk melakukannya di mesin atau permainan buatan sendiri.

Jelas akan ada pengorbanan, dan saya tidak bisa membayangkan bahwa Anda bisa mengkompilasi ulang kode Anda saat permainan dijeda, minta dimuat ulang dan bekerja di bawah setiap keadaan atau setiap platform.

Tapi mungkin itu mungkin untuk pemrograman AI dan modul logika level sederhana. Bukannya saya ingin melakukan itu dalam proyek apa pun dalam jangka pendek (atau jangka panjang), tetapi saya ingin tahu.

Jawaban:


26

Ini pasti mungkin dilakukan, meskipun tidak dengan kode C ++ khas Anda; Anda harus membuat pustaka gaya C yang dapat Anda tautkan secara dinamis dan muat ulang saat runtime. Untuk membuat ini mungkin, perpustakaan harus berisi semua negara di dalam sebuah pointer buram yang dapat diberikan ke perpustakaan saat memuat ulang.

Timothy Farrar membahas pendekatannya:

"Untuk pengembangan, kode dikompilasi sebagai pustaka, program loader kecil membuat salinan pustaka, dan memuat salinan pustaka untuk menjalankan program. Program mengalokasikan semua data saat startup, dan menggunakan satu penunjuk tunggal untuk referensi data apa pun. Saya tidak menggunakan apa pun global kecuali hanya membaca data. Sementara program sedang berjalan perpustakaan asli dapat dikompilasi ulang. Kemudian dalam satu tombol tekan, program kembali ke loader dan mengembalikan pointer data tunggal. Loader membuat salinan perpustakaan baru, memuat salinan, dan meneruskan di penunjuk data ke kode baru. Kemudian mesin melanjutkan di mana ia tinggalkan. " ( sumber asli , sekarang tersedia melalui arsip web )


Saya lebih suka ini daripada jawaban Blair, karena Anda benar-benar menjawab pertanyaan itu.
Jonathan Dickinson

15

Hot-swapping adalah masalah yang sangat, sangat sulit untuk dipecahkan dengan kode biner. Bahkan jika kode Anda ditempatkan di pustaka tautan dinamis yang terpisah, kode Anda perlu memastikan bahwa tidak ada referensi langsung ke fungsi dalam memori (karena alamat fungsi dapat berubah dengan kompilasi ulang). Ini pada dasarnya berarti tidak menggunakan fungsi virtual, dan apa pun yang menggunakan pointer fungsi melakukannya melalui semacam tabel pengiriman.

Hal-hal berbulu, membatasi. Lebih baik menggunakan bahasa scripting untuk kode yang membutuhkan iterasi cepat.

Di tempat kerja, basis kode kami saat ini adalah campuran dari C ++ dan Lua. Lua juga bukan bagian kecil dari proyek kami - hampir separuh 50/50. Kami telah menerapkan pemuatan ulang Lua dengan cepat, sehingga Anda dapat mengubah baris kode, memuat ulang, dan terus melanjutkan. Bahkan, Anda dapat melakukan ini untuk memperbaiki bug kerusakan yang terjadi dalam kode Lua, tanpa memulai ulang game!


3
Poin penting lainnya adalah bahwa bahkan jika Anda tidak berniat untuk menyimpan sistem dalam skrip, jika Anda berharap untuk mengulangi banyak sejak awal, mungkin masih sepenuhnya masuk akal untuk memulai di Lua, kemudian pindah ke C ++ di jalan ketika Anda membutuhkan kinerja ekstra dan tingkat iterasi telah melambat. Kelemahan yang jelas di sini adalah Anda akhirnya porting kode, tetapi banyak kali mendapatkan logika yang benar adalah bagian yang sulit - kode hampir menulis sendiri setelah Anda mengetahui bagian itu.
Logan Kincaid

15

(Anda mungkin ingin tahu tentang istilah "tambalan monyet" atau "meninju bebek" jika tidak lain dari citra mental lucu.)

Selain itu: jika tujuan Anda adalah mengurangi waktu iterasi untuk perubahan "perilaku", cobalah beberapa pendekatan yang membuat Anda hampir sampai di sana, dan gabungkan dengan baik untuk mengaktifkan lebih banyak hal ini di masa depan.

(Ini akan keluar dengan sedikit singgung, tapi aku berjanji itu akan kembali!)

  • Mulailah dengan data , dan mulai dari yang kecil: muat ulang pada batas ("level" atau sejenisnya), kemudian lanjutkan dengan menggunakan fungsionalitas OS untuk mendapatkan pemberitahuan perubahan file atau cukup jajak pendapat secara teratur.
  • (Untuk poin bonus dan waktu muat yang lebih rendah (sekali lagi, kurangi waktu iterasi) lihat ke pembuatan data .)
  • Script adalah data , dan memungkinkan Anda untuk mengulangi perilaku. Jika Anda menggunakan bahasa skrip, Anda sekarang memiliki notifikasi / kemampuan untuk memuat ulang skrip tersebut, ditafsirkan atau dikompilasi. Anda juga dapat menghubungkan juru bahasa Anda ke konsol dalam game, soket jaringan, atau sejenisnya untuk meningkatkan fleksibilitas runtime.
  • Kode juga dapat berupa data : kompiler Anda dapat mendukung overlay , pustaka bersama, DLL, atau sejenisnya. Jadi sekarang Anda dapat memilih waktu "aman" untuk membongkar dan memuat ulang overlay atau DLL, apakah manual atau otomatis. Jawaban lain masuk ke detail di sini. Perhatikan bahwa beberapa varian ini dapat mengacaukan verifikasi tanda tangan kriptografis, bit NX (tidak ada eksekusi) , atau mekanisme keamanan serupa.
  • Pertimbangkan sistem simpan / muat yang dalam dan berversi . Jika Anda dapat menyimpan dan memulihkan negara Anda dengan mantap bahkan dalam menghadapi perubahan kode, Anda dapat mematikan game Anda dan memulai kembali dengan logika baru pada titik yang sama persis. Lebih mudah diucapkan daripada dilakukan, tetapi itu bisa dilakukan, dan itu jauh lebih mudah dan lebih portabel daripada memencet memori untuk mengubah instruksi.
  • Bergantung pada struktur dan determinisme permainan Anda, Anda mungkin dapat melakukan perekaman dan pemutaran . Jika rekaman itu lebih dari "perintah permainan" (pikirkan permainan kartu, misalnya), Anda dapat mengubah semua kode render yang Anda inginkan, dan memutar ulang rekaman untuk melihat perubahan Anda. Untuk beberapa game, ini "semudah" merekam beberapa parameter awal (mis. Seed acak) dan kemudian aksi pengguna. Bagi sebagian orang itu jauh lebih rumit.
  • Berusaha untuk mengurangi waktu kompilasi . Dalam kombinasi dengan sistem save / load atau record / playback yang disebutkan di atas, atau bahkan dengan overlay atau DLL, ini dapat mengurangi turnaround Anda lebih dari hal lainnya.

Banyak dari poin-poin ini bermanfaat bahkan jika Anda tidak mendapatkan semua cara untuk memuat ulang data atau kode.

Anekdot pendukung:

Pada PC RTS besar (~ 120 orang tim, kebanyakan C ++), ada sistem penghematan yang sangat dalam, yang digunakan untuk setidaknya tiga tujuan:

  • Simpan "dangkal" diumpankan tidak ke disk tetapi ke mesin CRC untuk memastikan bahwa permainan multi pemain tetap dalam simulasi langkah-kunci satu CRC setiap 10-30 frame; ini memastikan tidak ada yang curang dan menangkap bug desync beberapa frame kemudian
  • Jika dan ketika bug desync multipemain terjadi, save ekstra-dalam dilakukan setiap frame, dan sekali lagi diumpankan ke engine CRC, tetapi kali ini engine CRC akan menghasilkan banyak CRC, masing-masing untuk batch byte yang lebih kecil. Dengan cara ini, ia bisa memberi tahu Anda dengan tepat bagian mana dari negara yang mulai menyimpang dalam bingkai terakhir. Kami menangkap perbedaan "default floating point mode" yang buruk antara AMD dan Intel menggunakan ini.
  • Penyelamatan kedalaman normal mungkin tidak menyimpan misalnya kerangka animasi unit Anda sedang diputar, tetapi ia akan mendapatkan posisi, kesehatan, dll dari semua unit Anda, memungkinkan Anda untuk menyimpan dan melanjutkan kapan saja selama bermain game.

Sejak itu saya menggunakan rekam deterministik / pemutaran pada permainan kartu C ++ dan Lua untuk DS. Kami terhubung ke API yang kami desain untuk AI (di sisi C ++) dan mencatat semua tindakan pengguna dan AI. Kami menggunakan fungsi ini dalam game (untuk memberikan replay untuk pemain), tetapi juga untuk mendiagnosis masalah: ketika ada kerusakan atau perilaku aneh, yang harus kami lakukan adalah mendapatkan file save dan memutarnya kembali dalam bentuk debug.

Saya juga telah menggunakan overlay lebih dari beberapa kali, dan kami menggabungkannya dengan "secara otomatis spider direktori ini dan mengunggah konten baru ke sistem handheld". Yang harus kita lakukan adalah meninggalkan cutscene / level / apa pun dan kembali lagi, dan tidak hanya data baru (sprite, tata letak level, dll) yang akan dimuat tetapi juga kode baru apa pun dalam overlay. Sayangnya itu semakin sulit dengan handheld yang lebih baru karena perlindungan terhadap penyalinan dan mekanisme anti-peretasan yang memperlakukan kode secara khusus. Kami masih melakukannya untuk skrip lua.

Last but not least: Anda dapat (dan saya punya, dalam berbagai keadaan spesifik yang sangat kecil) melakukan sedikit bebek meninju dengan menambal kode instruksi secara langsung. Ini berfungsi paling baik jika Anda menggunakan platform dan kompiler tetap, dan karena ini hampir tidak dapat dipelihara, sangat rentan terhadap bug, dan terbatas pada apa yang dapat Anda capai dengan cepat, saya kebanyakan hanya menggunakannya untuk merutekan ulang kode saat debugging. Ini tidak mengajarkan neraka banyak tentang arsitektur set instruksi Anda terburu-buru, meskipun.


6

Anda dapat melakukan sesuatu seperti hot-swapping modules pada saat runtime mengimplementasikan modul sebagai pustaka tautan dinamis (atau pustaka bersama di UNIX), dan menggunakan dlopen () dan dlsym () untuk memuat fungsi secara dinamik dari pustaka.

Untuk Windows, padanannya adalah LoadLibrary dan GetProcAddress.

Ini adalah metode C, dan memiliki beberapa kesulitan dengan menggunakannya dalam C ++, Anda dapat membaca tentang itu di sini .


panduan itu adalah kunci untuk membuat perpustakaan yang bisa ditukar, terima kasih
JqueryToAddNumbers

4

Jika Anda menggunakan Visual Studio C ++ Anda sebenarnya dapat menghentikan sebentar dan mengkompilasi ulang kode Anda dalam keadaan tertentu. Visual Studio mendukung Edit dan Lanjutkan . Lampirkan permainan Anda di debugger, buat berhenti di breakpoint, dan kemudian modifikasi kode setelah breakpoint Anda. Jika Anda ingin menyimpan dan kemudian melanjutkan, Visual Studio akan mencoba untuk mengkompilasi ulang kode, masukkan kembali ke dalam eksekusi yang dapat dijalankan, dan lanjutkan. Jika semuanya berjalan dengan baik, perubahan yang Anda buat akan langsung diterapkan ke permainan yang sedang berjalan tanpa harus melakukan seluruh siklus pengujian pembuatan kompilasi. Namun, hal berikut ini akan menghentikan ini berfungsi:

  1. Mengubah fungsi panggilan balik tidak akan berfungsi dengan baik. E&C bekerja dengan menambahkan potongan kode baru dan mengubah tabel panggilan untuk menunjuk ke blok baru. Untuk panggilan balik dan apa pun yang menggunakan fungsi pointer itu masih akan mengeksekusi panggilan lama yang tidak dimodifikasi. Untuk memperbaikinya, Anda dapat memiliki fungsi panggilan balik wrapper yang memanggil fungsi statis
  2. Memodifikasi file header hampir tidak akan berfungsi. Ini dirancang untuk memodifikasi panggilan fungsi aktual.
  3. Berbagai konstruksi bahasa akan menyebabkannya gagal secara misterius. Dari pengalaman pribadi saya, sebelum mendeklarasikan hal-hal seperti enum akan sering melakukan ini.

Saya telah menggunakan Edit dan Lanjutkan untuk secara dramatis meningkatkan kecepatan hal-hal seperti iterasi UI. Katakanlah Anda memiliki UI yang berfungsi sepenuhnya dibangun dalam kode kecuali bahwa Anda secara tidak sengaja menukar urutan dua kotak sehingga Anda tidak dapat melihat apa pun. Dengan mengubah 2 baris kode secara langsung, Anda dapat menyimpan sendiri siklus kompilasi / bangun / pengujian 20 menit untuk memeriksa perbaikan UI yang sepele.

Ini BUKAN solusi lengkap untuk lingkungan produksi, dan saya telah menemukan solusi terbaik untuk memindahkan sebanyak mungkin logika Anda ke dalam file data dan kemudian membuat file data tersebut dimuat ulang.


siklus kompilasi mana yang membutuhkan waktu 20 menit?!? saya lebih suka menembak diri saya sendiri
JqueryToAddNumbers

3

Seperti yang orang lain katakan, ini adalah masalah yang sulit, menghubungkan secara dinamis C ++. Tetapi ini adalah masalah yang terpecahkan - Anda mungkin pernah mendengar tentang COM atau salah satu nama pemasaran yang telah diterapkan selama bertahun-tahun: ActiveX.

COM memiliki sedikit nama yang buruk dari sudut pandang pengembang karena dapat banyak upaya untuk mengimplementasikan komponen C ++ yang mengekspos fungsionalitasnya menggunakannya (meskipun ini dipermudah dengan ATL - pustaka template ActiveX). Dari sudut pandang konsumen ia memiliki nama yang buruk karena aplikasi yang menggunakannya, misalnya untuk menanamkan lembar kerja Excel dalam dokumen Word atau diagram Visio dalam lembar kerja Excel, cenderung macet cukup lama di hari itu. Dan itu bermuara pada masalah yang sama - bahkan dengan semua panduan yang ditawarkan Microsoft, COM / ActiveX / OLE sulit untuk diperbaiki.

Saya akan menekankan bahwa teknologi COM itu sendiri pada dasarnya tidak buruk. Pertama-tama, DirectX menggunakan antarmuka COM untuk mengekspos fungsionalitasnya dan itu bekerja dengan cukup baik, seperti halnya banyak aplikasi yang menanamkan Internet Explorer menggunakan kontrol ActiveX itu. Kedua, ini adalah salah satu cara paling sederhana untuk secara dinamis menghubungkan kode C ++ - antarmuka COM pada dasarnya hanya kelas virtual murni. Meskipun memiliki IDL seperti CORBA, Anda tidak dipaksa untuk menggunakannya, terutama jika antarmuka yang Anda tetapkan hanya digunakan dalam proyek Anda.

Jika Anda tidak menulis untuk Windows, jangan berpikir bahwa COM tidak layak dipertimbangkan. Mozilla menerapkannya kembali dalam basis kode mereka (digunakan di browser Firefox) karena mereka membutuhkan cara untuk komponen kode C ++ mereka.


2

Ada implementasi C ++ compile runtime untuk kode gameplay di sini . Juga, saya tahu setidaknya ada satu mesin game berpemilik yang melakukan hal yang sama. Ini rumit, tetapi bisa dilakukan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.