Menggunakan Arsitektur Sistem Entitas dengan Paralelisme Berbasis Tugas


9

Latar Belakang

Saya telah bekerja untuk menciptakan mesin permainan multithreaded di waktu luang saya dan saat ini saya mencoba untuk memutuskan cara terbaik untuk bekerja dengan sistem entitas ke dalam apa yang telah saya buat. Sejauh ini, saya telah menggunakan artikel ini dari Intel sebagai titik awal untuk mesin saya. Sejauh ini saya telah mengimplementasikan loop permainan normal menggunakan tugas-tugas, dan saya sekarang pindah untuk mendapatkan beberapa sistem dan / atau entitas-sistem yang dimasukkan. Saya telah menggunakan sesuatu yang mirip dengan Artemis di masa lalu, tetapi paralelisme membuat saya pergi.

Artikel dari Intel tampaknya menganjurkan memiliki banyak salinan data entitas, dan perubahan yang dibuat untuk setiap entitas didistribusikan secara internal pada akhir pembaruan lengkap. Ini berarti bahwa rendering akan selalu menjadi satu kerangka di belakang, tetapi itu tampak seperti kompromi yang dapat diterima mengingat manfaat kinerja yang harus diperoleh. Namun ketika menyangkut sistem entitas seperti Artemis, setiap entitas digandakan untuk setiap sistem berarti setiap komponen juga perlu diduplikasi. Ini bisa dilakukan tetapi bagi saya sepertinya akan menghabiskan banyak memori. Bagian-bagian dari dokumen Intel yang membahas ini adalah 2.2 dan 3.2.2 terutama. Saya telah melakukan beberapa pencarian untuk melihat apakah saya dapat menemukan referensi yang bagus untuk mengintegrasikan arsitektur yang saya inginkan, tetapi saya belum dapat menemukan sesuatu yang bermanfaat.

Catatan: Saya menggunakan C ++ 11 untuk proyek ini, tetapi saya membayangkan sebagian besar dari apa yang saya tanyakan harus berupa bahasa agnostik yang cantik.

Kemungkinan Solusi

Memiliki EntityManager global yang digunakan untuk membuat dan mengelola Entitas dan EntityAttributes. Izinkan akses baca untuk mereka hanya selama fase pembaruan, dan simpan semua perubahan dalam antrian per utas. Setelah semua tugas selesai, antrian digabungkan dan perubahan di masing-masing diterapkan. Ini mungkin akan memiliki masalah dengan beberapa penulisan ke bidang yang sama tetapi saya yakin mungkin ada sistem prioritas atau cap waktu untuk mengatasinya. Ini sepertinya pendekatan yang baik bagi saya karena sistem dapat diberitahu tentang perubahan entitas secara alami selama tahap distribusi perubahan.

Pertanyaan

Saya mencari beberapa umpan balik tentang solusi saya untuk melihat apakah itu masuk akal. Saya tidak akan berbohong dan mengklaim sebagai ahli multithreading, dan saya melakukan ini sebagian besar untuk latihan. Saya dapat melihat beberapa kekacauan rumit yang muncul dari solusi saya di mana banyak sistem membaca / menulis beberapa nilai. Antrian perubahan yang saya sebutkan juga mungkin sulit untuk diformat sedemikian rupa sehingga setiap perubahan yang mungkin dapat dikomunikasikan dengan mudah ketika saya tidak bekerja dengan POD.

Umpan balik / saran akan sangat dihargai! Terima kasih!

Tautan

Jawaban:


12

Fork-Join

Anda tidak perlu salinan komponen yang terpisah. Cukup gunakan model garpu-gabung, yang (sangat buruk) disebutkan dalam artikel itu dari Intel.

Dalam ECS, Anda secara efektif memiliki lingkaran seperti:

while in game:
  for each system:
    for each component in system:
      update component

Ubah ini menjadi sesuatu seperti:

while in game:
  for each system:
    divide components into groups
    for each group:
      start thread (
        for each component in group:
          update component
      )
    wait for all threads to finish

Bagian yang sulit adalah bit "bagi komponen ke dalam kelompok". Untuk grafik, hampir tidak diperlukan data bersama sehingga sederhana (bagilah objek yang dapat diurai secara merata dengan jumlah utas pekerja yang tersedia). Untuk fisika dan AI, Anda ingin menemukan "pulau" logis dari objek yang tidak berinteraksi dan menyatukannya. Semakin sedikit interaksi antar komponen, semakin baik.

Untuk interaksi yang harus ada, pesan tertunda berfungsi paling baik. Jika objek A perlu memberi tahu objek B untuk menerima kerusakan, A bisa mengirimkan pesan ke kumpulan per-utas. Ketika benang-benang bergabung, semua kolam digabung menjadi satu kolam. Meskipun tidak terkait langsung dengan threading, lihat serangkaian acara artikel dari para pengembang BitSquid (pada kenyataannya, baca seluruh blog; Saya tidak setuju dengan semua yang ada di sana, tetapi ini adalah sumber yang fantastis).

Perhatikan bahwa "fork-join" tidak berarti menggunakan fork()(yang menciptakan proses, bukan utas), juga tidak menyiratkan bahwa Anda sebenarnya harus bergabung dengan utas. Ini hanya berarti bahwa Anda mengambil satu tugas, membagi menjadi beberapa bagian yang lebih kecil untuk ditangani oleh kumpulan pekerja Anda, dan kemudian menunggu semua paket diproses.

Proksi

Pendekatan ini dapat digunakan sendiri atau dikombinasikan dengan metode fork-join untuk membuat kebutuhan pemisahan yang ketat menjadi kurang penting.

Anda bisa lebih ramah dalam berinteraksi utas dengan menggunakan pendekatan dua lapis sederhana. Memiliki entitas "otoritatif" dan entitas "proxy". Entitas yang berwenang hanya dapat dimodifikasi dari satu utas yang merupakan pemilik yang jelas dari entitas yang berwenang. Entitas proxy tidak dapat dimodifikasi, hanya dibaca. Pada titik sinkronisasi di loop game, sebarkan semua perubahan dari entitas otoritatif ke proksi yang sesuai.

Ganti "entitas" dengan "komponen" yang sesuai. Intinya adalah bahwa Anda membutuhkan paling banyak dua salinan dari objek apa pun, dan ada poin "sinkronisasi" yang jelas dalam loop game Anda ketika Anda dapat menyalin dari satu ke yang lain dalam desain mesin game berulir paling waras.

Anda dapat memperluas proxy untuk masih memungkinkan (subset) metode / pesan yang akan digunakan dengan hanya memiliki semua hal-hal seperti itu maju ke dalam antrian yang dikirimkan ke objek otoritatif frame berikutnya.

Perhatikan bahwa pendekatan proxy adalah desain yang fantastis untuk dimiliki pada tingkat yang lebih tinggi karena membuat dukungan jaringan sangat mudah.


Saya telah membaca beberapa hal tentang pertigaan garpu yang Anda sebutkan sebelumnya dan saya mendapat kesan bahwa sementara itu memungkinkan Anda untuk memanfaatkan paralelisme, ada situasi di mana beberapa utas pekerja mungkin menunggu di satu kelompok untuk menyelesaikan. Idealnya, saya berusaha menghindari situasi itu. Gagasan proxy menarik dan sedikit menyerupai apa yang saya kerjakan. Entitas memiliki EntityAttributes dan itu adalah pembungkus untuk nilai sebenarnya yang disimpan oleh entitas. Jadi nilai bisa dibaca dari mereka kapan saja tetapi hanya ditetapkan pada waktu-waktu tertentu dan bisa mengandung nilai proxy dalam atribut, betul?
Ross Hays

1
Ada peluang bagus bahwa dalam upaya menghindari menunggu Anda menghabiskan begitu banyak waktu menganalisis grafik dependensi sehingga Anda kehilangan waktu secara keseluruhan.
Patrick Hughes

@roflha: ya, Anda bisa meletakkan proxy di tingkat EntityAttribute. Atau buat entitas terpisah dengan set atribut kedua. Atau cukup letakkan konsep atribut sama sekali dan gunakan desain komponen yang kurang granular.
Sean Middleditch

@SeanMiddleditch Ketika saya mengatakan atribut, saya pada dasarnya merujuk ke komponen yang saya pikir. Atribut bukan hanya nilai tunggal seperti float dan string jika memang seperti itu yang saya buat. Sebaliknya mereka adalah kelas yang berisi informasi spesifik seperti PositionAttribute. Jika komponen adalah nama yang diterima untuk itu maka mungkin saya harus berubah. Tetapi apakah Anda merekomendasikan proksi di tingkat entitas daripada tingkat komponen / atribut?
Ross Hays

1
Saya merekomendasikan apa pun yang menurut Anda paling mudah diterapkan. Hanya ingat bahwa titik saya akan dapat meminta proxy tanpa mengambil kunci apa pun, tanpa menggunakan atom apa pun, dan tanpa deadlock.
Sean Middleditch
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.