Apakah masuk akal untuk membangun aplikasi (bukan game) menggunakan arsitektur komponen-entitas-sistem?

24

Saya tahu bahwa ketika membangun aplikasi (asli atau web) seperti yang ada di Apple AppStore atau Google Play store store, sangat umum menggunakan arsitektur Model-View-Controller.

Namun, apakah masuk akal untuk juga membuat aplikasi menggunakan arsitektur Component-Entity-System yang umum di mesin game?

Andrew De Andrade
sumber
1
Lihatlah arsitektur Light Table: chris-granger.com/2013/01/24/the-ide-as-data
Hakan Deryal

Jawaban:

39

Namun, apakah masuk akal untuk juga membuat aplikasi menggunakan arsitektur Component-Entity-System yang umum di mesin game?

Bagi saya, tentu saja. Saya bekerja di FX visual dan mempelajari berbagai sistem di bidang ini, arsitekturnya (termasuk CAD / CAM), haus akan SDK dan makalah apa pun yang akan memberi saya rasa pro dan kontra dari keputusan arsitektur yang tampaknya tak terbatas yang dapat dibuat, bahkan dengan yang paling halus tidak selalu membuat dampak yang halus.

VFX agak mirip dengan game di mana ada satu konsep pusat "adegan", dengan viewports yang menampilkan hasil yang diberikan. Ada juga cenderung banyak proses gila tengah berputar di sekitar adegan ini terus-menerus dalam konteks animasi, di mana mungkin ada fisika terjadi, pemancar partikel partikel pemijahan, jerat yang dianimasi dan dirender, animasi gerak, dll, dan pada akhirnya untuk membuat mereka semua ke pengguna di akhir.

Konsep lain yang mirip dengan setidaknya mesin game yang sangat kompleks adalah kebutuhan akan aspek "perancang" di mana perancang dapat secara fleksibel merancang adegan, termasuk kemampuan melakukan pemrograman ringan sendiri (skrip dan simpul).

Saya menemukan, selama bertahun-tahun, bahwa ECS paling cocok. Tentu saja itu tidak pernah benar-benar bercerai dari subjektivitas, tetapi saya akan mengatakan itu tampaknya memberikan masalah paling sedikit. Itu memecahkan lebih banyak masalah besar yang selalu kami perjuangkan, sementara hanya memberi kami beberapa masalah kecil baru sebagai balasannya.

OOP tradisional

Pendekatan OOP yang lebih tradisional bisa sangat kuat ketika Anda memiliki pemahaman yang kuat tentang persyaratan desain di muka tetapi tidak persyaratan implementasi. Baik melalui pendekatan multi antarmuka yang lebih datar atau pendekatan ABC hierarkis yang lebih bertingkat, ia cenderung memperkuat desain dan membuatnya lebih sulit untuk diubah sambil membuat implementasi lebih mudah dan lebih aman untuk diubah. Selalu ada kebutuhan untuk ketidakstabilan dalam produk apa pun yang melewati versi tunggal, sehingga pendekatan OOP cenderung condong stabilitas (kesulitan perubahan dan kurangnya alasan untuk perubahan) menuju tingkat desain, dan ketidakstabilan (kemudahan perubahan dan alasan perubahan) ke tingkat implementasi.

Namun, terhadap berkembangnya persyaratan pengguna akhir, baik desain maupun implementasi mungkin perlu sering diubah. Anda mungkin menemukan sesuatu yang aneh seperti kebutuhan pengguna akhir yang kuat untuk makhluk analogis yang perlu menjadi tanaman dan hewan sekaligus, benar-benar membatalkan seluruh model konseptual yang Anda buat. Pendekatan berorientasi objek yang normal tidak melindungi Anda di sini, dan kadang-kadang dapat membuat perubahan pemecah konsep yang tidak terduga dan lebih sulit lagi. Ketika bidang yang sangat kritis terhadap kinerja dilibatkan, alasan perubahan desain semakin bertambah banyak.

Menggabungkan banyak, antarmuka granular untuk membentuk antarmuka yang sesuai dari suatu objek dapat banyak membantu dalam menstabilkan kode klien, tetapi itu tidak membantu dalam menstabilkan subtipe yang kadang-kadang bisa mengerdilkan jumlah dependensi klien. Anda dapat memiliki satu antarmuka yang digunakan hanya oleh sebagian dari sistem Anda, misalnya, tetapi dengan ribuan subtipe yang mengimplementasikan antarmuka itu. Dalam hal itu, mempertahankan subtipe kompleks (kompleks karena mereka memiliki begitu banyak tanggung jawab antarmuka yang berbeda untuk dipenuhi) dapat menjadi mimpi buruk daripada kode yang menggunakannya melalui antarmuka. OOP cenderung untuk mentransfer kompleksitas ke tingkat objek, sementara ECS mentransfernya ke tingkat klien ("sistem"), dan itu bisa ideal ketika ada sangat sedikit sistem tetapi sejumlah besar yang sesuai dengan "objek" ("entitas").

masukkan deskripsi gambar di sini

Sebuah kelas juga memiliki datanya secara pribadi, dan dengan demikian dapat mempertahankan invarian semuanya sendiri. Namun demikian, ada invarian "kasar" yang sebenarnya masih sulit dipertahankan ketika objek berinteraksi satu sama lain. Agar sistem yang kompleks secara keseluruhan berada dalam kondisi yang valid, sering kali perlu mempertimbangkan grafik objek yang kompleks, bahkan jika invarian masing-masingnya dipertahankan dengan baik. Pendekatan gaya-OOP tradisional dapat membantu mempertahankan invarian granular, tetapi sebenarnya dapat membuat sulit untuk mempertahankan invarian luas dan kasar jika objek fokus pada aspek kecil sistem.

Di situlah pendekatan atau varian ECS lego-block-building semacam ini bisa sangat membantu. Juga dengan sistem yang lebih kasar dalam desain daripada objek biasa, menjadi lebih mudah untuk mempertahankan jenis invarian kasar pada pandangan mata burung dari sistem. Banyak interaksi objek kecil berubah menjadi satu sistem besar yang berfokus pada satu tugas besar alih-alih objek kecil mungil yang berfokus pada tugas-tugas kecil dengan grafik ketergantungan yang akan mencakup satu kilometer kertas.

Namun saya harus melihat keluar dari bidang saya, di industri game, untuk belajar tentang ECS, meskipun saya selalu salah satu dari pola pikir yang berorientasi data. Juga, cukup lucu, saya hampir membuat jalan saya menuju ECS sendiri hanya iterasi dan mencoba untuk datang dengan desain yang lebih baik. Saya tidak berhasil sampai sejauh itu dan melewatkan detail yang sangat penting, yang merupakan formalisasi dari bagian "sistem", dan meremas komponen sampai ke data mentah.

Saya akan mencoba menjelaskan bagaimana saya akhirnya memilih ECS, dan bagaimana akhirnya menyelesaikan semua masalah dengan iterasi desain sebelumnya. Saya pikir itu akan membantu menyoroti mengapa jawaban di sini bisa sangat kuat "ya", bahwa ECS berpotensi berlaku jauh melampaui industri game.

1980-an Arsitektur Brute Force

Arsitektur pertama yang saya kerjakan di industri VFX memiliki warisan panjang yang sudah melewati satu dekade sejak saya bergabung dengan perusahaan. Itu kasar kasar C coding sepanjang jalan (bukan miring pada C, karena saya suka C, tapi cara itu digunakan di sini benar-benar kasar). Irisan miniatur dan terlalu sederhana menyerupai dependensi seperti ini:

masukkan deskripsi gambar di sini

Dan ini adalah diagram yang sangat disederhanakan dari satu bagian kecil dari sistem. Setiap klien ini dalam diagram ("Rendering", "Fisika", "Motion") akan mendapatkan beberapa objek "generik" di mana mereka akan memeriksa bidang jenis, seperti:

void transform(struct Object* obj, const float mat[16])
{
    switch (obj->type)
    {
        case camera:
            // cast to camera and do something with camera fields
            break;
        case light:
            // cast to light and do something with light fields
            break;
        ...
    }
}

Tentu saja dengan kode yang jauh lebih jelek dan lebih kompleks dari ini. Seringkali fungsi-fungsi tambahan akan dipanggil dari sakelar-sakelar sakelar ini yang secara rekursif akan melakukan sakelar berulang-ulang. Diagram dan kode ini mungkin hampir terlihat seperti ECS-lite, tetapi tidak ada perbedaan entitas-komponen yang kuat (" apakah objek ini kamera?", Bukan "apakah objek ini memberikan gerakan?"), Dan tidak ada formalisasi "sistem" ( hanya sekelompok fungsi bersarang di semua tempat dan menggabungkan tanggung jawab). Dalam hal itu, hampir semuanya rumit, fungsi apa pun merupakan potensi bencana yang menunggu untuk terjadi.

Prosedur pengujian kami di sini sering harus memeriksa hal-hal seperti jerat yang terpisah dari jenis barang lainnya, bahkan jika hal yang sama terjadi pada keduanya, karena sifat kasar dari pengkodean di sini (sering disertai dengan banyak salinan dan tempel) sering dilakukan sangat mungkin bahwa apa yang sebaliknya logika yang sama bisa gagal dari satu jenis item ke yang berikutnya. Mencoba memperluas sistem untuk menangani jenis barang baru itu cukup sia-sia, meskipun ada kebutuhan pengguna akhir yang sangat kuat, karena terlalu sulit ketika kami berjuang sangat keras hanya untuk menangani jenis barang yang ada.

Beberapa pro:

  • Uhh ... kurasa tidak butuh pengalaman teknik apa pun? Sistem ini tidak memerlukan pengetahuan konsep-konsep dasar seperti polimorfisme, itu benar-benar kasar, jadi saya kira bahkan seorang pemula pun mungkin dapat memahami beberapa kode bahkan jika pro di debugging hampir tidak dapat mempertahankannya.

Beberapa kontra:

  • Mimpi buruk pemeliharaan. Tim pemasaran kami benar-benar merasa perlu untuk membanggakan kami memperbaiki lebih dari 2000 bug unik dalam satu siklus 3 tahun. Bagi saya itu adalah sesuatu yang memalukan bahwa kami memiliki begitu banyak bug di tempat pertama, dan proses itu mungkin masih hanya memperbaiki sekitar 10% dari total bug yang terus bertambah jumlahnya sepanjang waktu.
  • Tentang solusi yang paling tidak fleksibel.

Arsitektur COM 1990-an

Sebagian besar industri VFX menggunakan gaya arsitektur ini dari apa yang telah saya kumpulkan, membaca dokumen tentang keputusan desain mereka dan melirik kit pengembangan perangkat lunak mereka.

Ini mungkin bukan COM pada level ABI (beberapa arsitektur ini hanya bisa memiliki plugin yang ditulis menggunakan kompiler yang sama), tetapi berbagi banyak karakteristik serupa dengan kueri antarmuka yang dibuat pada objek untuk melihat antarmuka apa yang didukung komponen mereka.

masukkan deskripsi gambar di sini

Dengan pendekatan semacam ini, transformfungsi analogis di atas menyerupai bentuk ini:

void transform(Object obj, const Matrix& mat)
{
    // Wrapper that performs an interface query to see if the 
    // object implements the IMotion interface.
    MotionRef motion(obj);

    // If the object supported the IMotion interface:
    if (motion.valid())
    {
        // Transform the item through the IMotion interface.
        motion->transform(mat);
        ...
    }
}

Ini adalah pendekatan tim baru dari basis kode lama yang menetap, untuk akhirnya refactor ke arah. Dan itu adalah peningkatan dramatis dibandingkan yang asli dalam hal fleksibilitas dan pemeliharaan, tetapi masih ada beberapa masalah yang akan saya bahas di bagian selanjutnya.

Beberapa pro:

  • Secara dramatis lebih fleksibel / dapat diperpanjang / dipelihara daripada solusi brute force sebelumnya.
  • Mempromosikan kesesuaian yang kuat dengan banyak prinsip SOLID dengan membuat setiap antarmuka sepenuhnya abstrak (stateless, tanpa implementasi, hanya antarmuka murni).

Beberapa kontra:

  • Banyak boilerplate. Komponen kami harus dipublikasikan melalui registri untuk membuat instance objek, antarmuka yang didukungnya membutuhkan antarmuka ("implementasi" di Jawa) yang mewarisi antarmuka dan menyediakan beberapa kode untuk menunjukkan antarmuka mana yang tersedia dalam kueri.
  • Mempromosikan logika duplikat di semua tempat sebagai hasil dari antarmuka murni. Misalnya, semua komponen yang diimplementasikan IMotionakan selalu memiliki status yang sama persis dan implementasi yang sama persis untuk semua fungsi. Untuk mengurangi ini, kami akan mulai memusatkan kelas-kelas dasar dan fungsionalitas pembantu di seluruh sistem untuk hal-hal yang cenderung diterapkan secara berlebihan dengan cara yang sama untuk antarmuka yang sama, dan mungkin dengan beberapa pewarisan yang terjadi di belakang tenda, tapi itu cukup berantakan di bawah tenda meskipun kode klien mudah.
  • Inefisiensi: sesi vtune sering menunjukkan QueryInterfacefungsi dasar yang hampir selalu muncul sebagai hotspot menengah ke atas, dan kadang-kadang bahkan hotspot # 1. Untuk mengurangi itu, kami akan melakukan hal-hal seperti memiliki merender bagian dari basis kode cache daftar objek yang sudah dikenal untuk mendukungIRenderable, tapi itu secara signifikan meningkatkan kompleksitas dan biaya perawatan. Demikian juga, ini lebih sulit untuk diukur tetapi kami melihat beberapa perlambatan yang pasti dibandingkan dengan pengkodean C-style yang kami lakukan sebelumnya ketika setiap antarmuka tunggal memerlukan pengiriman dinamis. Hal-hal seperti mispredictions cabang dan hambatan optimisasi sulit diukur di luar aspek kode yang sedikit, tetapi para pengguna umumnya memperhatikan responsif dari antarmuka pengguna dan hal-hal seperti itu semakin buruk dengan membandingkan versi sebelumnya dan yang lebih baru dari perangkat lunak secara berdampingan. sisi untuk area di mana kompleksitas algoritmik tidak berubah, hanya konstanta.
  • Masih sulit untuk alasan tentang kebenaran di tingkat sistem yang lebih luas. Meskipun secara signifikan lebih mudah daripada pendekatan sebelumnya, masih sulit untuk memahami interaksi kompleks antara objek-objek di seluruh sistem ini, terutama dengan beberapa optimasi yang mulai menjadi perlu untuk melawannya.
  • Kami mengalami kesulitan untuk memperbaiki antarmuka kami. Meskipun mungkin hanya ada satu tempat luas dalam sistem yang menggunakan antarmuka, persyaratan pengguna akhir akan berubah dari versi, dan kami akhirnya harus melakukan perubahan cascading ke semua kelas yang mengimplementasikan antarmuka untuk mengakomodasi fungsi baru yang ditambahkan ke antarmuka, misalnya, kecuali ada beberapa kelas dasar abstrak yang sudah memusatkan logika di bawah tenda (beberapa di antaranya akan terwujud di tengah-tengah perubahan mengalir dengan harapan tidak berulang kali melakukan ini berulang-ulang).

masukkan deskripsi gambar di sini

Respon Pragmatis: Komposisi

Salah satu hal yang kami perhatikan sebelumnya (atau setidaknya saya adalah) yang menyebabkan masalah adalah yang IMotionmungkin diimplementasikan oleh 100 kelas yang berbeda tetapi dengan implementasi yang sama persis dan negara terkait. Selain itu, itu hanya akan digunakan oleh beberapa sistem seperti rendering, gerakan keyframed, dan fisika.

Jadi dalam kasus seperti itu, kita mungkin memiliki hubungan 3-ke-1 antara sistem yang menggunakan antarmuka ke antarmuka, dan hubungan 100-ke-1 antara subtipe yang mengimplementasikan antarmuka ke antarmuka.

Kompleksitas dan pemeliharaan kemudian akan secara drastis condong ke implementasi dan pemeliharaan 100 subtipe, bukan 3 sistem klien yang bergantung IMotion. Ini menggeser semua kesulitan perawatan kami ke pemeliharaan 100 subtipe ini, bukan 3 tempat yang menggunakan antarmuka. Memperbarui 3 tempat dalam kode dengan sedikit atau tidak ada "kopling eferen tidak langsung" (seperti dalam ketergantungannya tetapi secara tidak langsung melalui antarmuka, bukan ketergantungan langsung), bukan masalah besar: memperbarui 100 tempat subtipe dengan muatan kapal "kopling eferen tidak langsung" , masalah besar *.

* Saya menyadari itu aneh dan salah untuk mengacaukan definisi "kopling efferent" dalam hal ini dari perspektif implementasi, saya hanya belum menemukan cara yang lebih baik untuk menggambarkan kompleksitas pemeliharaan yang terkait ketika antarmuka maupun implementasi yang sesuai dari seratus subtipe harus berubah.

Jadi saya harus berusaha keras tetapi saya mengusulkan agar kami mencoba menjadi sedikit lebih pragmatis dan mengendurkan seluruh ide "antarmuka murni". Tidak masuk akal bagi saya untuk membuat sesuatu yang IMotionsepenuhnya abstrak dan tanpa kewarganegaraan kecuali kita melihat manfaatnya memiliki beragam implementasi. Dalam kasus kami, IMotionmemiliki beragam implementasi yang kaya akan benar-benar berubah menjadi mimpi buruk pemeliharaan, karena kami tidak menginginkan variasi. Alih-alih, kami melakukan iterasi untuk mencoba membuat implementasi gerakan tunggal yang benar-benar bagus terhadap perubahan kebutuhan klien, dan sering kali bekerja di sekitar ide antarmuka murni yang banyak mencoba memaksa setiap implementor IMotionuntuk menggunakan implementasi yang sama dan status yang terkait sehingga kami tidak t tujuan rangkap.

Antarmuka menjadi lebih luas Behaviorsterkait dengan entitas. IMotionhanya akan menjadi Motion"komponen" (saya mengubah cara kami mendefinisikan "komponen" dari COM ke yang lebih dekat dengan definisi biasa, dari bagian yang membentuk entitas "lengkap").

Alih-alih ini:

class IMotion
{
public:
    virtual ~IMotion() {}
    virtual void transform(const Matrix& mat) = 0;
    ...
};

Kami mengembangkannya menjadi sesuatu yang lebih seperti ini:

class Motion
{
public:
    void transform(const Matrix& mat)
    {
        ...
    }
    ...

private:
    Matrix transformation;
    ...
};

Ini adalah pelanggaran terang-terangan dari prinsip inversi ketergantungan untuk mulai bergeser dari abstrak kembali ke beton, tetapi bagi saya tingkat abstraksi seperti itu hanya berguna jika kita dapat meramalkan kebutuhan asli di masa depan, di luar keraguan yang masuk akal dan tidak menggunakan skenario "bagaimana jika" yang konyol sepenuhnya terlepas dari pengalaman pengguna (yang mungkin akan memerlukan perubahan desain), untuk fleksibilitas seperti itu.

Jadi kami mulai berevolusi ke desain ini. QueryInterfacemenjadi lebih seperti QueryBehavior. Lebih jauh lagi, mulai tampak tidak ada gunanya menggunakan warisan di sini. Kami menggunakan komposisi sebagai gantinya. Objek berubah menjadi kumpulan komponen yang ketersediaannya dapat ditanyakan dan disuntikkan pada saat runtime.

masukkan deskripsi gambar di sini

Beberapa pro:

  • Jauh lebih mudah untuk dipertahankan dalam kasus kami daripada sistem COM-style murni antarmuka sebelumnya. Kejutan yang tak terduga seperti perubahan persyaratan atau keluhan alur kerja dapat ditampung lebih mudah dengan satu Motionimplementasi yang sangat sentral dan jelas , misalnya, dan tidak tersebar di seratus subtipe.
  • Memberi tingkat fleksibilitas yang sama sekali baru dari jenis yang sebenarnya kami butuhkan. Dalam sistem kami sebelumnya, karena model pewarisan hubungan statis, kami hanya dapat secara efektif mendefinisikan entitas baru pada waktu kompilasi dalam C ++. Kami tidak dapat melakukannya dari bahasa scripting, mis. Dengan pendekatan komposisi, kami dapat merangkai entitas baru dengan cepat saat runtime dengan hanya melampirkan komponen ke dalamnya dan menambahkannya ke daftar. "Entitas" berubah menjadi kanvas kosong yang dengannya kita bisa mengumpulkan kolase dari apa pun yang kita butuhkan dengan cepat, dengan sistem yang relevan secara otomatis mengenali dan memproses entitas ini sebagai hasilnya.

Beberapa kontra:

  • Kami masih mengalami kesulitan di departemen efisiensi, dan pemeliharaan di bidang-bidang yang kritis terhadap kinerja. Setiap sistem akan tetap ingin menyimpan komponen entitas yang menyediakan perilaku ini untuk menghindari perulangan berulang kali dan memeriksa apa yang tersedia. Setiap sistem yang menuntut kinerja akan melakukan hal ini sedikit berbeda, dan rentan terhadap serangkaian bug yang berbeda karena gagal memperbarui daftar cache ini dan mungkin struktur data (jika beberapa bentuk pencarian terlibat seperti frustum culling atau raytracing) pada beberapa peristiwa perubahan adegan yang tidak jelas, misalnya
  • Masih ada sesuatu yang canggung dan kompleks yang tidak bisa saya letakkan di jari saya terkait dengan semua benda kecil yang berperilaku dan sederhana ini. Kami masih menelurkan banyak peristiwa untuk berurusan dengan interaksi antara objek-objek "perilaku" yang kadang-kadang diperlukan, dan hasilnya adalah kode yang sangat terdesentralisasi. Setiap objek kecil mudah untuk diuji kebenarannya, dan diambil secara individual, sering kali benar. Namun rasanya masih seperti kami mencoba untuk mempertahankan ekosistem besar yang terdiri dari desa-desa kecil dan mencoba untuk berpikir tentang apa yang mereka semua lakukan secara individu dan tambahkan untuk dijadikan sebagai keseluruhan. C-style 80-an basis kode terasa seperti satu epik, kelebihan populasi megalopolis yang jelas merupakan mimpi buruk pemeliharaan,
  • Kehilangan fleksibilitas dengan kurangnya abstraksi tetapi di daerah di mana kita tidak pernah benar-benar menemukan kebutuhan yang tulus untuk itu, jadi hampir tidak ada yang praktis (meskipun pasti setidaknya satu teoritis).
  • Mempertahankan kompatibilitas ABI selalu sulit, dan ini membuatnya lebih sulit dengan memerlukan data yang stabil dan tidak hanya antarmuka yang stabil yang terkait dengan "perilaku". Namun, kami dapat dengan mudah menambahkan perilaku baru dan hanya mencela yang sudah ada jika diperlukan perubahan keadaan, dan itu bisa dibilang lebih mudah daripada melakukan backflip di bawah antarmuka di tingkat subtipe untuk menangani masalah versi.

Salah satu fenomena yang terjadi adalah, karena kami kehilangan abstraksi pada komponen perilaku ini, kami memiliki lebih dari itu. Misalnya, alih-alih IRenderablekomponen abstrak , kami akan melampirkan objek dengan beton Meshatau PointSpriteskomponen. Sistem rendering akan tahu cara membuat Meshdan PointSpriteskomponen dan akan menemukan entitas yang menyediakan komponen tersebut dan menggambar itu. Di lain waktu, kami memiliki renderables lain-lain seperti SceneLabelyang kami temukan kami butuhkan di belakang, dan jadi kami akan melampirkan SceneLabeldalam kasus-kasus tersebut ke entitas yang relevan (mungkin selain a Mesh). Implementasi sistem rendering kemudian akan diperbarui untuk mengetahui cara membuat entitas yang menyediakannya, dan itu adalah perubahan yang cukup mudah dibuat.

Dalam hal ini, entitas yang terdiri dari komponen juga dapat digunakan sebagai komponen untuk entitas lain. Kami akan membangun hal-hal seperti itu dengan memasang blok lego.

ECS: Sistem dan Komponen Data Mentah

Sistem terakhir sejauh yang saya buat sendiri, dan kami masih membastardisasi dengan COM. Rasanya seperti ingin menjadi sistem entitas-entitas, tetapi saya tidak terbiasa dengannya saat itu. Saya melihat-lihat contoh gaya COM yang memenuhi bidang saya, ketika saya seharusnya mencari mesin game AAA untuk inspirasi arsitektur. Saya akhirnya mulai melakukan itu.

Apa yang saya lewatkan adalah beberapa ide kunci:

  1. Formalisasi "sistem" untuk memproses "komponen".
  2. "Komponen" adalah data mentah dan bukan objek perilaku yang disusun bersama menjadi objek yang lebih besar.
  3. Entitas tidak lebih dari ID ketat yang terkait dengan kumpulan komponen.

Saya akhirnya meninggalkan perusahaan itu dan mulai mengerjakan ECS sebagai indy (masih mengerjakannya sambil menguras tabungan saya), dan itu merupakan sistem yang paling mudah untuk dikelola sejauh ini.

Apa yang saya perhatikan dengan pendekatan ECS adalah bahwa itu menyelesaikan masalah yang masih saya perjuangkan di atas. Yang paling penting bagi saya, rasanya seperti kami mengelola "kota-kota" berukuran sehat dan bukannya desa kecil dengan interaksi yang kompleks. Itu tidak sulit untuk dipertahankan sebagai "megalopolis" monolitik, terlalu besar dalam populasi untuk dikelola secara efektif, tetapi tidak semrawut seperti dunia yang dipenuhi dengan desa-desa kecil yang berinteraksi satu sama lain di mana hanya memikirkan rute perdagangan di di antara mereka membentuk grafik mimpi buruk. ECS menyaring semua kompleksitas menuju "sistem" besar, seperti sistem render, "kota" berukuran sehat tetapi bukan "megalopolis yang terlalu padat".

Komponen yang menjadi data mentah terasa sangat aneh bagi saya pada awalnya, karena bahkan merusak prinsip dasar penyembunyian informasi OOP. Itu agak menantang salah satu nilai terbesar yang saya sayangi tentang OOP, yang merupakan kemampuannya untuk mempertahankan invarian yang membutuhkan enkapsulasi dan penyembunyian informasi. Tapi itu mulai menjadi bukan masalah karena dengan cepat menjadi jelas apa yang terjadi dengan hanya selusin sistem yang luas mengubah data itu bukannya logika seperti itu tersebar di ratusan hingga ribuan subtipe yang menerapkan kombinasi antarmuka. Saya cenderung menganggapnya seperti masih dalam gaya OOP kecuali menyebar di mana sistem menyediakan fungsionalitas dan implementasi yang mengakses data, komponen menyediakan data, dan entitas menyediakan komponen.

Menjadi lebih mudah , kontra-intuitif, untuk memikirkan tentang efek samping yang disebabkan oleh sistem ketika hanya ada beberapa sistem besar mengubah data dalam melewati lebar. Sistem menjadi lebih "rata", tumpukan panggilan saya menjadi lebih dangkal dari sebelumnya untuk setiap utas. Saya bisa memikirkan sistem di tingkat pengawas itu dan tidak mengalami kejutan aneh.

Demikian juga, hal itu membuat area yang sangat kritis terhadap kinerja menjadi sederhana sehubungan dengan penghapusan pertanyaan itu. Karena gagasan "Sistem" menjadi sangat formal, sistem dapat berlangganan komponen yang diminati, dan hanya menyerahkan daftar entitas yang di-cache yang memenuhi kriteria tersebut. Setiap individu tidak perlu mengelola optimasi caching itu, ia menjadi terpusat ke satu tempat.

Beberapa pro:

  • Tampaknya hanya menyelesaikan hampir setiap masalah arsitektur utama yang saya temui dalam karir saya tanpa pernah merasa terjebak dalam sudut desain ketika menghadapi kebutuhan yang tidak terduga.

Beberapa kontra:

  • Saya masih mengalami kesulitan membungkus kepala saya kadang-kadang, dan itu bukan paradigma yang paling matang atau mapan bahkan dalam industri game, di mana orang berdebat tentang apa artinya dan bagaimana melakukan sesuatu. Ini jelas bukan sesuatu yang bisa saya lakukan dengan mantan tim tempat saya bekerja, yang terdiri dari anggota yang sangat terkait dengan pola pikir gaya-COM atau pola pikir gaya-1980-an dari basis kode asli. Di mana saya kadang-kadang bingung seperti bagaimana memodelkan hubungan gaya-grafik antara komponen, tetapi saya selalu menemukan solusi yang tidak berubah menjadi mengerikan nanti di mana saya bisa membuat komponen bergantung pada komponen lain ("gerakan ini) komponen tergantung pada yang lain ini sebagai orangtua, dan sistem akan menggunakan memoisasi untuk menghindari berulang kali melakukan perhitungan gerak rekursif yang sama ", misalnya)
  • ABI masih sulit, tetapi sejauh ini saya bahkan berani mengatakan bahwa itu lebih mudah daripada pendekatan antarmuka murni. Ini adalah perubahan pola pikir: stabilitas data menjadi fokus tunggal untuk ABI, daripada stabilitas antarmuka, dan dalam beberapa hal lebih mudah untuk mencapai stabilitas data daripada stabilitas antarmuka (mis: tidak ada godaan untuk mengubah fungsi hanya karena membutuhkan parameter baru. Hal-hal semacam itu terjadi di dalam implementasi sistem kasar yang tidak merusak ABI).

masukkan deskripsi gambar di sini

Namun, apakah masuk akal untuk juga membuat aplikasi menggunakan arsitektur Component-Entity-System yang umum di mesin game?

Jadi, saya akan mengatakan "ya", dengan contoh VFX pribadi saya menjadi kandidat yang kuat. Tapi itu masih cukup mirip dengan kebutuhan gaming.

Saya belum mempraktikkannya di daerah terpencil yang sepenuhnya terlepas dari kekhawatiran mesin game (VFX sangat mirip), tetapi bagi saya sepertinya lebih banyak area yang merupakan kandidat yang baik untuk pendekatan ECS. Mungkin bahkan sistem GUI akan cocok untuk satu, tapi saya masih menggunakan pendekatan OOP lebih di sana (tetapi tanpa warisan yang mendalam tidak seperti Qt, misalnya).

Ini adalah wilayah yang belum dijelajahi secara luas, tetapi tampaknya cocok untuk saya setiap kali entitas Anda dapat terdiri dari kombinasi "sifat" yang kaya (dan kombinasi karakter apa yang mereka dapat berubah), dan di mana Anda memiliki beberapa generalisasi sistem yang memproses entitas yang memiliki sifat yang diperlukan.

Dalam kasus-kasus itu menjadi alternatif yang sangat praktis untuk skenario apa pun di mana Anda mungkin tergoda untuk menggunakan sesuatu seperti pewarisan berganda atau emulasi konsep (mixin, misalnya) hanya untuk menghasilkan ratusan atau lebih kombo dalam hierarki warisan yang dalam atau ratusan kombo. kelas dalam hierarki datar yang mengimplementasikan kombo antarmuka tertentu, tetapi di mana sistem Anda sedikit jumlahnya (puluhan, misalnya).

Dalam kasus tersebut, kompleksitas basis kode mulai terasa lebih proporsional dengan jumlah sistem daripada jumlah kombinasi jenis, karena masing-masing jenis sekarang hanya entitas yang menyusun komponen yang tidak lebih dari data mentah. Sistem GUI secara alami cocok dengan jenis spesifikasi ini di mana mereka mungkin memiliki ratusan jenis widget yang mungkin digabungkan dari jenis dasar atau antarmuka lainnya, tetapi hanya beberapa sistem untuk memprosesnya (sistem tata letak, sistem render, dll.). Jika sistem GUI menggunakan ECS, mungkin akan jauh lebih mudah untuk alasan tentang kebenaran sistem ketika semua fungsionalitas disediakan oleh segelintir sistem ini daripada ratusan jenis objek yang berbeda dengan antarmuka yang diwariskan atau kelas dasar. Jika sistem GUI menggunakan ECS, widget tidak akan memiliki fungsionalitas, hanya data. Hanya segelintir sistem yang memproses entitas widget yang memiliki fungsionalitas. Bagaimana peristiwa yang bisa ditimpa widget akan ditangani di luar saya, tetapi hanya berdasarkan pengalaman saya yang terbatas sejauh ini, saya belum menemukan kasus di mana jenis logika tidak dapat ditransfer secara terpusat ke sistem yang diberikan dengan cara yang, dalam Tinjau kembali, menghasilkan solusi yang jauh lebih elegan yang pernah saya harapkan.

Saya ingin melihatnya bekerja di lebih banyak bidang, karena itu adalah penyelamat di tambang. Tentu saja itu tidak cocok jika desain Anda tidak rusak dengan cara ini, dari entitas yang mengumpulkan komponen ke sistem kasar yang memproses komponen-komponen itu, tetapi jika mereka secara alami sesuai dengan model semacam ini, itu adalah hal paling indah yang pernah saya temui. .

Thomas Owens
sumber
1) Apa yang dilakukan contoh program VFX Anda dari sudut pandang pengguna? 2) Proyek ECS apa yang sedang Anda kerjakan sekarang? ♥ Terima kasih telah menulis ini! ♥
pup
1
Penjelasan yang sangat menyeluruh - terima kasih. Saya merasa seperti saya sampai pada banyak kesimpulan yang sama dengan Anda sehubungan dengan bagaimana ECS berlaku di luar permainan; dalam kasus saya GUI khusus kompleks. Pada mulanya terasa sangat aneh untuk menentang apa yang biasanya dilakukan (hierarki warisan mendalam menjadi sangat menonjol dalam kerangka kerja UI), tetapi menggembirakan melihat orang lain yang menemukan pendekatan ini lebih efektif.
Danny Yaroslavski
1
Terima kasih atas ... artikel hebat ini! Untuk GUI berbasis komponen saya akan merekomendasikan untuk melihat UGUI Unity3d. Ini sangat fleksibel dan dapat diperpanjang dibandingkan dengan yang berbasis warisan seperti CocoaTouch.
Ivan Mir
16

Arsitektur Component-Entity-System untuk mesin game berfungsi untuk game karena sifat perangkat lunak game, dan karakteristik unik serta persyaratan kualitasnya. Sebagai contoh, entitas menyediakan cara seragam untuk menangani dan bekerja dengan hal-hal dalam permainan, yang mungkin berbeda secara drastis dalam tujuan dan penggunaannya, tetapi perlu dirender, diperbarui, atau diserialisasi / diserialisasi oleh sistem dengan cara yang seragam. Dengan memasukkan model komponen ke dalam arsitektur ini, Anda memungkinkan mereka untuk menjaga struktur inti yang sederhana, sambil menambahkan lebih banyak fitur dan fungsi sesuai kebutuhan, dengan kopling kode rendah. Ada sejumlah sistem perangkat lunak yang berbeda yang dapat mengambil manfaat dari karakteristik desain ini, seperti aplikasi CAD, codec A / V,

TL; DR - Pola desain hanya berfungsi dengan baik ketika domain masalah cukup sesuai dengan fitur dan kelemahan yang mereka timbulkan pada desain.

Shotgun Ninja
sumber
8

Jika domain masalah cocok untuk itu, tentu saja.

Pekerjaan saya saat ini melibatkan aplikasi yang perlu mendukung berbagai kemampuan tergantung pada banyak faktor runtime. Menggunakan entitas berbasis komponen untuk memisahkan semua kemampuan itu dan memungkinkan ekstensibilitas dan testabilitas dalam isolasi sangat ideal bagi kami.

sunting: Pekerjaan saya melibatkan penyediaan konektivitas ke perangkat keras berpemilik (dalam C #). Bergantung pada faktor-bentuk perangkat kerasnya, firmware apa yang dipasang di sana, tingkat layanan apa yang telah dibeli oleh klien, dll., Dll. Kita perlu menyediakan tingkat fungsionalitas yang berbeda untuk perangkat. Bahkan beberapa fitur yang memiliki antarmuka yang sama memiliki implementasi yang berbeda tergantung pada versi perangkatnya.

Basis kode sebelumnya di sini memiliki antarmuka yang sangat luas dan banyak yang tidak diimplementasikan. Beberapa memiliki banyak antarmuka tipis yang kemudian disusun secara statis dalam satu kelas beasty. Beberapa hanya menggunakan string -> kamus string untuk memodelkannya. (kami memiliki banyak departemen yang semuanya berpikir mereka dapat melakukannya dengan lebih baik)

Ini semua memiliki kekurangan. Antarmuka yang lebar adalah rasa sakit setengah untuk mengejek / menguji secara efektif. Menambahkan fitur baru berarti mengubah antarmuka publik (dan semua implementasi yang ada). Banyak antarmuka yang tipis menyebabkan kode yang dikonsumsi sangat jelek, tetapi karena kami akhirnya melewati pengujian objek besar lemak masih menderita. Plus antarmuka yang tipis tidak mengelola dependensi mereka dengan baik. Kamus string memiliki masalah penguraian dan eksistensi yang biasa dan juga lubang neraka kinerja, keterbacaan, dan pemeliharaan.

Apa yang kami gunakan sekarang adalah entitas yang sangat ramping yang komponennya ditemukan dan disusun berdasarkan info runtime. Dependensi dilakukan secara deklaratif dan diselesaikan secara otomatis oleh kerangka kerja komponen inti. Komponen itu sendiri dapat diuji secara terpisah karena mereka bekerja secara langsung dengan dependensinya, dan masalah dengan dependensi yang hilang ditemukan lebih awal - dan di satu lokasi daripada penggunaan pertama dependensi. Komponen baru (atau pengujian) dapat dimasukkan dan tidak ada kode yang terpengaruh olehnya. Konsumen meminta entitas untuk antarmuka ke komponen, jadi kami bebas untuk bermain-main dengan berbagai implementasi (dan bagaimana implementasi dipetakan ke data runtime) dengan kebebasan relatif.

Untuk situasi seperti ini di mana komposisi objek dan interface-nya dapat mencakup beberapa (sangat bervariasi) komponen umum, ia bekerja dengan sangat baik.

Telastyn
sumber
1
Dengan asumsi Anda diizinkan, dapatkah Anda memberikan detail lebih lanjut tentang pekerjaan Anda saat ini? Saya ingin tahu bagaimana cara CES sangat ideal untuk apa yang Anda bangun.
Andrew De Andrade
Apakah ada artikel, tulisan, atau blog tentang pengalaman Anda? juga, saya ingin memiliki lebih banyak rincian teknis tentang hal itu :)
user1778770
@ user1778770 - tidak tersedia untuk umum, tidak. Pertanyaan macam apa yang Anda miliki?
Telastyn
Baiklah, mari kita mulai dengan sesuatu yang sederhana, apakah konsep Anda menjangkau seluruh tumpukan aplikasi (mis. Dari bisnis ke frontend)? atau hanya satu lapisan kasus penggunaan tunggal?
user1778770
@ user1778770 - dalam implementasi saya, entitas / komponen ada dalam satu lapisan. Entitas yang berbeda mungkin ada di lapisan yang berbeda, tetapi sering kali bukan 1: 1 (atau lapisan tidak memberikan manfaat).
Telastyn