Mengelompokkan entitas dari komponen yang sama diatur ke dalam memori linier


11

Kita mulai dari pendekatan sistem-komponen-entitas dasar .

Mari kita membuat kumpulan (istilah yang berasal dari artikel ini ) hanya dari informasi tentang jenis komponen . Ini dilakukan secara dinamis pada saat runtime, sama seperti kita akan menambah / menghapus komponen ke entitas satu per satu, tapi mari kita beri nama lebih tepatnya karena ini hanya tentang jenis informasi.

Kemudian kami membangun entitas yang menentukan kumpulan untuk masing-masing. Setelah kami membuat entitas, kumpulannya tidak dapat diubah yang artinya kami tidak dapat memodifikasinya secara langsung, tetapi kami masih dapat memperoleh tanda tangan entitas yang ada untuk salinan lokal (bersama dengan konten), membuat perubahan yang tepat untuknya, dan membuat entitas baru keluar itu.

Sekarang untuk konsep kunci: setiap kali suatu entitas dibuat, ia ditugaskan ke objek yang disebut assemblage bucket , yang berarti bahwa semua entitas dengan tanda tangan yang sama akan berada dalam wadah yang sama (misalnya dalam std :: vector).

Sekarang sistem hanya beralih melalui setiap ember minat mereka dan melakukan pekerjaan mereka.

Pendekatan ini memiliki beberapa keunggulan:

  • komponen disimpan dalam beberapa (tepatnya: jumlah ember) potongan memori yang berdekatan - ini meningkatkan keramahan memori dan lebih mudah untuk membuang seluruh kondisi permainan
  • komponen proses sistem secara linier, yang berarti peningkatan koherensi cache - bye bye kamus dan lompatan memori acak
  • membuat entitas baru semudah memetakan kumpulan ke bucket dan mendorong kembali komponen yang diperlukan ke vektornya
  • menghapus entitas semudah satu panggilan ke std :: pindah untuk menukar elemen terakhir dengan yang dihapus, karena pesanan tidak masalah saat ini

masukkan deskripsi gambar di sini

Jika kita memiliki banyak entitas dengan tanda tangan yang sama sekali berbeda, manfaat koherensi cache agak berkurang, tetapi saya tidak berpikir itu akan terjadi di sebagian besar aplikasi.

Ada juga masalah dengan pembatalan pointer setelah vektor dialokasikan kembali - ini bisa diselesaikan dengan memperkenalkan struktur seperti:

struct assemblage_bucket {
    struct entity_watcher {
        assemblage_bucket* owner;
        entity_id real_index_in_vector;
    };

    std::unordered_map<entity_id, std::vector<entity_watcher*>> subscribers;

    //...
};

Jadi, setiap kali karena alasan tertentu dalam logika permainan kami, kami ingin melacak entitas yang baru dibuat, di dalam bucket kami mendaftarkan entitas_watcher , dan begitu entitas tersebut harus std :: move'd selama penghapusan, kami mencari pengamat dan memperbarui mereka real_index_in_vectornilai-nilai baru. Sebagian besar waktu ini hanya memaksakan pencarian kamus tunggal untuk setiap penghapusan entitas.

Apakah ada lagi kelemahan dari pendekatan ini?

Mengapa solusinya tidak disebutkan, meskipun cukup jelas?

EDIT : Saya mengedit pertanyaan untuk "menjawab jawaban", karena komentar tidak cukup.

Anda kehilangan sifat dinamis komponen pluggable, yang dibuat khusus untuk menjauh dari konstruksi kelas statis.

Bukan saya. Mungkin saya tidak menjelaskannya dengan cukup jelas:

auto signature = world.get_signature(entity_id); // this would just return entity_id.bucket_owner->bucket_signature or so
signature.add(foo_component);
signature.remove(bar_component);
world.delete_entity(entity_id); // entity_id would hold information about its bucket owner
world.create_entity(signature); // automatically assigns new entity to an existing or a new bucket

Sesederhana mengambil tanda tangan entitas yang ada, mengubahnya dan mengunggah lagi sebagai entitas baru. Sifat pluggable, dinamis ? Tentu saja. Di sini saya ingin menekankan bahwa hanya ada satu kelas "kumpulan" dan satu "ember". Bucket didorong oleh data dan dibuat saat runtime dalam jumlah yang optimal.

Anda harus melewati semua ember yang mungkin berisi target yang valid. Tanpa struktur data eksternal, deteksi tabrakan bisa sama sulitnya.

Nah, inilah mengapa kita memiliki struktur data eksternal yang disebutkan di atas . Solusinya sesederhana memperkenalkan iterator di kelas Sistem yang mendeteksi kapan harus melompat ke bucket berikutnya. The melompat akan murni transparan untuk logika.


Saya juga membaca artikel Randy Gaul tentang menyimpan semua komponen dalam vektor dan membiarkan sistem mereka memprosesnya. Saya melihat dua masalah besar di sana: bagaimana jika saya ingin memperbarui hanya sub-set entitas (pikirkan culling misalnya). Karena itu komponen akan ditambah lagi dengan entitas. Untuk setiap langkah iterasi komponen, saya harus memeriksa apakah entitas yang dimilikinya telah dipilih untuk pembaruan. Masalah lainnya adalah bahwa beberapa sistem perlu memproses beberapa jenis komponen yang berbeda dengan mengambil keuntungan koherensi cache lagi. Ada ide bagaimana menangani masalah ini?
tiguchi

Jawaban:


7

Anda pada dasarnya merancang sistem objek statis dengan pengalokasi kumpulan dan dengan kelas dinamis.

Saya menulis sistem objek yang bekerja hampir identik dengan sistem "kumpulan" Anda di masa sekolah saya, meskipun saya selalu cenderung menyebut "kumpulan" baik "cetak biru" atau "pola dasar" dalam desain saya sendiri. Arsitekturnya lebih merepotkan daripada sistem objek yang naif dan tidak memiliki keunggulan kinerja yang terukur dibandingkan beberapa desain yang lebih fleksibel yang saya bandingkan. Kemampuan untuk memodifikasi objek secara dinamis tanpa perlu mengubahnya atau mengalokasikannya kembali sangat penting ketika Anda bekerja pada editor game. Desainer ingin menyeret komponen ke definisi objek Anda. Kode Runtime bahkan mungkin perlu memodifikasi komponen secara efisien dalam beberapa desain, meskipun saya pribadi tidak menyukainya. Bergantung pada bagaimana Anda menghubungkan referensi objek di editor Anda,

Anda akan mendapatkan koherensi cache yang lebih buruk daripada yang Anda pikirkan dalam kebanyakan kasus non-sepele. Sistem AI Anda misalnya tidak peduli tentang Renderkomponen tetapi akhirnya terjebak iterating atas mereka sebagai bagian dari masing-masing entitas. Objek yang diiterasi lebih besar, dan permintaan cacheline akhirnya menarik data yang tidak perlu, dan lebih sedikit keseluruhan objek dikembalikan dengan setiap permintaan). Itu masih akan lebih baik daripada metode naif, dan komposisi objek metode naif digunakan bahkan di mesin AAA besar, jadi Anda mungkin tidak perlu lebih baik, tapi setidaknya jangan berpikir Anda tidak bisa memperbaikinya lebih lanjut.

Pendekatan Anda memang masuk akal bagi sebagian orangkomponen, tetapi tidak semua. Saya sangat tidak menyukai ECS karena mendukung selalu menempatkan setiap komponen dalam wadah terpisah, yang masuk akal untuk fisika atau grafik atau yang lainnya tetapi tidak masuk akal sama sekali jika Anda mengizinkan beberapa komponen skrip atau AI komposer. Jika Anda membiarkan sistem komponen digunakan untuk lebih dari sekadar objek bawaan tetapi juga sebagai cara bagi perancang dan pemrogram gameplay untuk menyusun perilaku objek, maka masuk akal untuk mengelompokkan semua komponen AI (yang akan sering berinteraksi) atau semua skrip komponen (karena Anda ingin memperbarui semuanya dalam satu batch). Jika Anda menginginkan sistem yang paling berkinerja maka Anda akan memerlukan campuran alokasi komponen dan skema penyimpanan dan meluangkan waktu untuk mencari tahu dengan pasti mana yang terbaik untuk setiap jenis komponen tertentu.


Saya berkata: kita tidak dapat mengubah tanda tangan entitas, dan saya maksudkan bahwa kita tidak dapat secara langsung memodifikasinya di tempat, tetapi tetap saja kita bisa mendapatkan kumpulan yang ada ke salinan lokal, membuat perubahan padanya, dan mengunggah lagi sebagai entitas baru - dan ini operasi cukup murah, seperti yang saya tunjukkan dalam pertanyaan. Sekali lagi - hanya ada SATU kelas "ember". "Assemblages" / "Signatures" / "beri nama apa pun yang kita inginkan" dapat dibuat secara dinamis saat runtime seperti dalam pendekatan standar, saya bahkan akan berpikir entitas sebagai "tanda tangan".
Patryk Czachurski

Dan saya katakan Anda tidak perlu ingin berurusan dengan reifikasi. "Membuat entitas baru" berpotensi dapat berarti memecah semua pegangan yang ada ke entitas, tergantung pada cara kerja sistem pegangan Anda. Panggilan Anda jika cukup murah atau tidak. Saya menemukan itu hanya menjadi sakit di pantat harus berurusan dengan.
Sean Middleditch

Oke, sekarang saya punya poin tentang hal ini. Pokoknya saya berpikir bahwa bahkan jika menambah / menghapus sedikit lebih mahal itu terjadi begitu banyak sehingga masih sangat layak menyederhanakan proses mengakses komponen, yang terjadi secara real-time. Jadi, biaya overhead "perubahan" dapat diabaikan. Tentang contoh AI Anda, bukankah masih layak beberapa sistem ini yang tetap membutuhkan data dari beberapa komponen?
Patryk Czachurski

Maksud saya di sana adalah bahwa AI adalah tempat di mana pendekatan Anda lebih baik, tetapi untuk komponen lain belum tentu demikian.
Sean Middleditch

4

Apa yang telah Anda lakukan adalah merekayasa ulang objek C ++. Alasan mengapa ini terasa jelas adalah bahwa jika Anda mengganti kata "entitas" dengan "kelas" dan "komponen" dengan "anggota" ini adalah desain OOP standar menggunakan mixin.

1) Anda kehilangan sifat dinamis komponen pluggable, yang dibuat khusus untuk menjauh dari konstruksi kelas statis.

2) koherensi memori paling penting dalam tipe data, bukan dalam objek menyatukan beberapa tipe data di satu tempat. Ini adalah salah satu alasan bahwa komponen + sistem diciptakan, untuk menjauh dari fragmen memori objek kelas +.

3) desain ini juga kembali ke gaya kelas C ++ karena Anda memikirkan entitas sebagai objek yang koheren ketika, dalam desain komponen + sistem, entitas tersebut hanya berupa tag / ID untuk membuat pekerjaan batin dapat dimengerti oleh manusia.

4) sama mudahnya bagi komponen untuk membuat serial sendiri daripada objek kompleks untuk membuat serial beberapa komponen dalam dirinya sendiri, jika tidak benar-benar lebih mudah untuk dicatat sebagai programmer.

5) langkah logis berikutnya di jalur ini adalah menghapus Sistem dan memasukkan kode itu langsung ke entitas, di mana ia memiliki semua data yang diperlukan untuk bekerja. Kita semua bisa melihat apa artinya =)


2) mungkin saya tidak mengerti caching sepenuhnya, tetapi katakanlah ada Sistem yang berfungsi dengan mengatakan 10 komponen. Dalam pendekatan standar, memproses setiap entitas berarti mengakses RAM 10 kali, karena komponen tersebar di tempat-tempat acak dalam memori, bahkan jika kumpulan digunakan - karena komponen yang berbeda dimiliki oleh kumpulan yang berbeda. Tidakkah menjadi "penting" untuk meng-cache seluruh entitas sekaligus dan memproses semua komponen tanpa satu cache yang hilang, bahkan tanpa harus membuat pencarian kamus? Juga, saya membuat edit untuk membahas poin 1)
Patryk Czachurski

@Sean Middleditch memiliki deskripsi yang bagus tentang pemecahan caching ini dalam jawabannya.
Patrick Hughes

3) Mereka bukan objek yang koheren dengan cara apa pun. Mengenai komponen A yang berada tepat di sebelah komponen B dalam memori, itu hanya "koherensi memori", bukan "koherensi logis", seperti yang ditunjukkan oleh John. Bucket, pada kreasi mereka, bahkan dapat mengocok komponen dalam tanda tangan dengan urutan dan prinsip yang diinginkan masih akan dipertahankan. 4) mungkin sama mudahnya untuk "melacak" jika kita memiliki cukup abstraksi - apa yang kita bicarakan hanyalah skema penyimpanan yang disediakan dengan iterator dan mungkin byte peta offset dapat membuat pemrosesan semudah dalam pendekatan standar.
Patryk Czachurski

5) Dan saya tidak berpikir apa-apa dalam ide ini menunjuk ke arah ini. Bukannya saya tidak ingin setuju dengan Anda, saya hanya ingin tahu di mana diskusi ini dapat mengarah, meskipun bagaimanapun itu mungkin akan mengarah pada semacam "mengukurnya" atau "optimasi prematur" yang terkenal. :)
Patryk Czachurski

@ PatrykCzachurski tetapi sistem Anda tidak bekerja dengan 10 komponen.
user253751

3

Menyatukan entitas seperti itu tidak sepenting yang Anda pikirkan, itulah sebabnya sulit untuk memikirkan alasan yang valid selain "karena itu adalah unit". Tetapi karena Anda benar-benar melakukan ini untuk koherensi cache yang bertentangan dengan koherensi logis, mungkin masuk akal.

Salah satu kesulitan yang bisa Anda miliki adalah interaksi antara komponen dalam ember yang berbeda. Ini tidak terlalu mudah untuk menemukan sesuatu yang dapat ditembaki oleh AI Anda, misalnya, Anda harus melewati semua ember yang mungkin berisi target yang valid. Tanpa struktur data eksternal, deteksi tabrakan bisa sama sulitnya.

Untuk melanjutkan tentang pengorganisasian entitas bersama untuk koherensi logis, satu-satunya alasan bahwa saya harus menjaga entitas bersama adalah untuk tujuan identifikasi dalam misi saya. Saya perlu tahu apakah Anda baru saja membuat entitas tipe A atau tipe B, dan saya menyiasatinya dengan ... Anda dapat menebaknya: menambahkan komponen baru yang mengidentifikasi kumpulan yang menyatukan entitas ini. Meski begitu, saya tidak mengumpulkan semua komponen untuk tugas besar, saya hanya perlu tahu apa itu. Jadi saya tidak berpikir bagian ini sangat berguna.


Saya harus mengakui bahwa saya tidak begitu mengerti jawaban Anda. Apa yang Anda maksud dengan "koherensi logis"? Tentang kesulitan dalam interaksi, saya mengedit.
Patryk Czachurski

"Logical coherence", seperti pada: Ini masuk akal "logis" untuk menjaga semua komponen yang membentuk entitas Tree berdekatan.
John McDonald
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.