Bir bileşen-varlık-sistem mimarisi kullanarak uygulamalar (oyunlar değil) inşa etmek mantıklı mıdır?


24

Apple AppStore veya Google Play uygulama mağazasında olduğu gibi uygulamalar oluştururken (yerel veya web) Model-View-Controller mimarisi kullanmanın çok yaygın olduğunu biliyorum.

Bununla birlikte, oyun motorlarında ortak olan Bileşen-Varlık-Sistem mimarisini kullanarak uygulamalar oluşturmak makul olur mu?


Yanıtlar:


39

Bununla birlikte, oyun motorlarında ortak olan Bileşen-Varlık-Sistem mimarisini kullanarak uygulamalar oluşturmak makul olur mu?

Bana göre kesinlikle. Visual FX'te çalışıyorum ve bu alanda çok çeşitli sistemler, mimarileri (CAD / CAM dahil), SDK'lar için aç ve bana görünüşte sonsuz mimari kararların artıları ve eksileri hakkında bir fikir verebilecek makaleler okudum. Her zaman ince bir etki yaratmayan en ince bile olsa bile yapılabilir.

VFX, elde edilen sonuçları görüntüleyen vitrinlerle bir "sahne" merkezi kavramının olduğu oyunlara oldukça benzer. Ayrıca, bu sahnenin etrafında sürekli olarak devam eden bir dizi merkezi döngüsel işlem söz konusudur; bu, fiziğin olabileceği, parçacıkların yayıldığı parçacık yayıcıları, canlandırılan ve işlenen kafesler, hareket animasyonları, vb. sonunda tüm kullanıcıya.

En azından çok karmaşık oyun motorlarına benzer başka bir konsept, tasarımcıların esnek bir şekilde kendi tasarımlarını (betikler ve düğümler) biraz programlama yapma yetenekleri de dahil olmak üzere sahneleri tasarlayabilecekleri bir "tasarımcı" yönüne olan ihtiyaçtı.

Yıllar geçtikçe ECS'nin en iyi sonucu verdiğini öğrendim. Tabii ki bu hiçbir zaman öznellikten tamamen ayrılmadı, ama en az sorunu çözdüğü kesin olarak ortaya çıktı. Her zaman uğraştığımız çok daha büyük problemleri çözdü, karşılığında bize sadece birkaç yeni küçük problem verdi.

Geleneksel OOP

Daha geleneksel OOP yaklaşımları, tasarım gerekliliklerini önceden görüp, uygulama gerekliliklerini kesin olarak anlamadığınızda gerçekten güçlü olabilir. Daha basit çoklu arabirim yaklaşımı veya daha iç içe geçmiş bir hiyerarşik ABC yaklaşımı sayesinde, tasarımı değiştirmek ve uygulamayı değiştirmek daha kolay ve daha güvenli hale getirirken değiştirmeyi zorlaştırır. Tek bir versiyondan geçen herhangi bir üründe her zaman dengesizliğe ihtiyaç vardır, bu nedenle OOP yaklaşımları tasarım seviyesine karşı istikrarı (değişim zorluğu ve değişiklik nedenleri eksikliği) ve kararsızlığı (değişim kolaylığı ve değişim nedenleri) eğriltme eğilimindedir. uygulama seviyesine.

Ancak, gelişen kullanıcı sonu gereksinimlerine karşı, hem tasarım hem de uygulama sık sık değişebilir. Hem bitki hem de hayvan olması gereken analog yaratığa aynı anda yarattığınız tüm kavramsal modeli tamamen geçersiz kılan güçlü bir kullanıcı sonu ihtiyacı gibi garip bir şey bulabilirsiniz. Normal nesne yönelimli yaklaşımlar sizi burada korumaz ve bazen böyle beklenmeyen, kavram kırıcı değişiklikleri zorlaştırabilir. Çok kritik performans alanları söz konusu olduğunda, tasarımın nedenleri daha da artar.

Bir nesnenin uygun arayüzünü oluşturmak için çoklu, tanecikli arayüzlerin birleştirilmesi, müşteri kodunun dengelenmesinde çok yardımcı olabilir, ancak bazen müşteri bağımlılıklarının sayısını caydırabilecek alt türlerin dengelenmesinde yardımcı olmaz. Örneğin, sisteminizin sadece bir kısmı tarafından kullanılan, ancak bu arayüzü uygulayan bin farklı alt tipte bir arayüz kullanabilirsiniz. Bu durumda, karmaşık alt tiplerin (karmaşık olarak yerine getirilmesi gereken çok fazla arabirim sorumluluğuna sahip oldukları için) korunması, bir arabirim aracılığıyla bunları kullanan koddan ziyade kabus olabilir. OOP, karmaşıklığı nesne seviyesine transfer etme eğilimindeyken, ECS bunu istemci ("sistemler") seviyesine aktarır ve çok az sistem olduğunda ancak "nesneler" ("varlıklar") ile uyumlu bir sürü grup olduğunda ideal olabilir.

görüntü tanımını buraya girin

Bir sınıf ayrıca kendi verisine özel olarak sahip olur ve böylece değişmezleri kendi başına tutabilir. Bununla birlikte, nesneler birbirleriyle etkileşime girdiğinde bakımı hala zor olabilen "kaba" değişmezler vardır. Karmaşık bir sistemin bir bütün olarak geçerli bir durumda olması için, bireysel değişmezleri düzgün bir şekilde korunsa bile, karmaşık bir nesne grafiğini göz önünde bulundurması gerekir. Geleneksel OOP tarzı yaklaşımlar, granül değişmezlerin korunmasına yardımcı olabilir, ancak nesneler sistemin ufacık fasetlerine odaklanırsa, geniş, kaba değişmezlerin korunmasını gerçekten zorlaştırabilir.

Bu tür lego-blok bina ECS yaklaşımlarının veya varyantlarının bu kadar faydalı olabileceği yerler. Sistemlerin tasarımda olağan nesnelerden daha kaba olmasıyla, bu tür kaba değişmezlerin sistemin kuşbakışı görünümünde tutulması daha kolay hale gelir. Birçok ufacık nesne etkileşimi, bir kilometrelik bir kağıdı kapsayacak bir bağımlılık grafiğiyle ufacık ufak görevlere odaklanan ufacık ufak nesneler yerine geniş bir göreve odaklanan büyük bir sisteme dönüşür.

Yine de, her zaman veri odaklı bir zihniyetten biri olduğum halde, oyun endüstrisindeki ECS hakkında bilgi edinmek için alanımın dışına bakmak zorunda kaldım. Ayrıca, yeterince komik, neredeyse sadece ECS yolunda kendi başıma iter ve daha iyi tasarımlar bulmaya çalışıyorum. Yine de hepsini yapmadım ve “sistemler” bölümünün biçimlendirilmesi ve bileşenleri çiğ verilere indirgemek için çok önemli bir detayı kaçırdım.

ECS'ye nasıl yerleştiğimi ve önceki tasarım yinelemeleriyle tüm sorunları nasıl çözdüğümü incelemeye çalışacağım. Bence buradaki cevabın neden çok güçlü bir "evet" olabileceğini, ECS'nin oyun endüstrisinin ötesinde potansiyel olarak uygulanabilir olduğunu vurgulamaya yardımcı olacağını düşünüyorum.

1980'lerin Brute Force Mimarisi

VFX endüstrisinde çalıştığım ilk mimarinin, şirkete katılmamdan bu yana, on yıldan fazla süredir devam eden uzun bir mirası vardı. Tüm yolu C kodlayan kaba kuvvetdi (C'yi sevdiğim için C üzerinde eğik değil, ama burada kullanıldığı şekli gerçekten çok kabaydı). Minyatür ve aşırı basit bir dilim, bunun gibi bağımlılıklara benziyordu:

görüntü tanımını buraya girin

Ve bu sistemin küçük bir parçasının çok basitleştirilmiş bir diyagramıdır. Şemadaki bu istemcilerden her biri ("Rendering", "Physics", "Motion"), şöyle bir tür alanı kontrol edecekleri "jenerik" bir nesne alacaktır, şöyle:

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;
        ...
    }
}

Elbette bundan daha çirkin ve daha karmaşık bir kodla. Genelde, tekrar tekrar tekrar tekrar tekrar tekrar anahtar yapacak olan bu anahtar durumlarından ek fonksiyonlar çağrılır. Bu şema ve kod neredeyse ECS-lite gibi görünebilir, ancak hiçbir güçlü varlık bileşenli ayrım ( "olduğunu olduğunu bu nesne kamera?" Değil "bu nesne yok sağlamak (hareketi?') Ve 'sistem' hiçbir kayıtlılığı Her yere kadar bir sürü iç içe geçmiş fonksiyon vardır ve sorumlulukları karıştırırlar). Bu durumda, hemen hemen her şey karmaşıktı, herhangi bir işlev gerçekleşmeyi bekleyen bir felaket için potansiyeldi.

Buradaki test prosedürümüz, aynı anda her ikisinde de olsa bile, diğer kod türlerinden ayrı kafesler gibi şeyleri kontrol etmek zorunda kalmıştır; çünkü burada kodlamanın kaba kuvveti (çoğu kez kopyala ve yapıştır ile birlikte) sıklıkla yapılmıştır. Aksi halde, aynı mantığın ne olduğu bir öğe türünden diğerine başarısız olabilir. Sistemi, yeni türdeki öğeleri ele alacak şekilde genişletmeye çalışmak, oldukça açık bir şekilde ifade edilmiş bir kullanıcı sonu gereksinimi olmasına rağmen, sadece mevcut türdeki öğeleri ele almak için çok uğraştığımız için çok zordu.

Bazı artılar:

  • Uhh ... mühendislik tecrübesi almaz sanırım? Bu sistem polimorfizm, hatta kaba kaba kuvvet gibi temel kavramlar hakkında hiçbir bilgi gerektirmez, bu yüzden bir hata ayıklama uzmanının zorlukla koruyabilmesine rağmen, yeni başlayanlar bile bazı kodları anlayabilir.

Bazı eksileri:

  • Bakım kabusu. Pazarlama ekibimiz aslında 3 yıllık bir döngüde 2000'den fazla benzersiz hatayı düzeltmemiz gerektiğini vurguladı. Bana göre bu utanılacak bir şey, ilk etapta çok fazla hatamız vardı ve bu süreç hala her zaman sayıca artan böcek toplamının sadece% 10'unu sabitledi.
  • Mümkün olan en esnek çözüm hakkında.

1990'ların COM Mimarisi

VFX endüstrisinin çoğu, topladıklarımdan bu tarz mimariyi kullanıyor, tasarım kararlarıyla ilgili belgeleri okuyor ve yazılım geliştirme kitlerine bakıyor.

ABI düzeyinde tam olarak COM olmayabilir (bu mimarilerin bazıları sadece aynı derleyici kullanılarak yazılmış eklentilere sahip olabilir), ancak bileşenlerinin hangi arayüzleri desteklediğini görmek için nesneler üzerinde yapılan arayüz sorguları ile benzer özellikleri paylaşır.

görüntü tanımını buraya girin

Bu tür bir yaklaşımla, transformyukarıdaki analog fonksiyon bu forma benzemeye başladı:

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);
        ...
    }
}

Bu eski kod üssünün yeni ekibinin kararlaştırdığı ve sonunda yeniden yönlendirici olduğu yaklaşım. Ve orijinal ve esneklik ve sürdürülebilirlik açısından çarpıcı bir gelişme oldu, ama yine de bir sonraki bölümde ele alacağım bazı konular vardı.

Bazı artılar:

  • Önemli ölçüde, önceki kaba kuvvet çözümünden çok daha esnek / genişletilebilir / sürdürülebilir.
  • Her arayüzü tamamen soyut yaparak (vatansız, uygulama yok, sadece saf arayüzler), SOLID'in birçok ilkesine güçlü bir uyum sağlar.

Bazı eksileri:

  • Çok fazla kazan plakası. Nesnelerin somutlaştırılması için bileşenlerimizin bir kayıt defteri aracılığıyla yayınlanması gerekiyordu, destekledikleri arabirimler hem arabirimin miras alınması ("Java'da" uygulanması ") hem de sorguda hangi arabirimlerin kullanılabilir olduğunu belirtmek için bazı kodlar sağlamaları gerekiyordu.
  • Saf arayüzlerin bir sonucu olarak, her yere yayılmış kopyalanmış mantık. Örneğin, uygulanan tüm bileşenlerIMotion her zaman tüm fonksiyonlar için aynı aynı duruma ve aynı uygulamaya sahiptir. Bunu hafifletmek için, aynı arayüz için yedekli olarak uygulanma eğiliminde olan şeyler için aynı sınıfta ve muhtemelen kaputun arkasında devam eden çoklu kalıtımla ilgili şeyler için temel sınıfları ve yardımcı işlevselliği merkezileştirmeye başlıyoruz. Müşteri kodu kolay olmasına rağmen başlık altında dağınık.
  • Verimsizlik: vtune oturumları genellikle temel QueryInterfaceişlevi neredeyse her zaman bir orta-üst sıcak nokta ve bazen de # 1 sıcak nokta olarak ortaya çıkaran temel işlevi gösterdi . Bunu hafifletmek için, kod tabanı önbelleğinin parçalarını zaten desteklediği bilinen nesnelerin bir listesini oluşturma gibi şeyler yaparızIRenderableAncak bu, karmaşıklık ve bakım maliyetlerini önemli ölçüde arttırdı. Aynı şekilde, bunu ölçmek daha zordu, ancak her bir arayüzün dinamik bir gönderim gerektirdiği zamanlar yaptığımız C tarzı kodlamaya kıyasla kesin bir yavaşlama olduğunu fark ettik. Şube yanlış tahminleri ve optimizasyon engelleri gibi şeyler küçük bir kod yönü dışında ölçmek zordur, ancak kullanıcılar genellikle kullanıcı arayüzünün duyarlılığını ve yazılımın önceki ve daha yeni sürümlerini yan yana karşılaştırarak daha da kötüye gittiğini fark ederlerdi. algoritmik karmaşıklığın değişmediği alanların yanı sıra sadece sabitler de vardır.
  • Daha geniş bir sistem düzeyinde doğruluk nedeni hala zordu. Önceki yaklaşımdan çok daha kolay olsa da, bu sistemdeki nesneler arasındaki karmaşık etkileşimi, özellikle de buna karşı gerekli olmaya başlayan optimizasyonların bazılarını kavramak hala zordu.
  • Arayüzlerimizi doğru bulmakta zorlandık. Arabirim kullanan sistemde yalnızca tek bir geniş alan olsa bile, kullanıcı sonu gereksinimleri sürümlere göre değişecekti ve eklenmiş yeni bir işlevi barındırmak için arabirimi uygulayan tüm sınıflarda basamaklı değişiklikler yapmak zorunda kalacağız. arayüz, örneğin, mantığı kaputun altında merkezileştiren bazı soyut temel sınıflar olmadığı sürece (bunlardan bazıları bunu tekrar tekrar yapmama umuduyla bu basamaklı değişikliklerin ortasında gösterecektir).

görüntü tanımını buraya girin

Pragmatik Yanıt: Kompozisyon

Sorunlara neden olan şeylerden biriydi (ya da en azından öyleydim). IMotion 100 farklı sınıf tarafından uygulanabilecek ancak aynı uygulama ve durum ile aynıydı. Ayrıca, sadece oluşturma, ana kare hareketi ve fizik gibi bir avuç sistem tarafından kullanılır.

Bu nedenle, böyle bir durumda, ara yüze arayüzü kullanan sistemler arasındaki 3e-1 bir ilişki ve ara-yüzeyi ara yüze uygulayan alt-tipler arasında 100-1 bir ilişki gibi olabiliriz.

O zaman karmaşıklık ve bakım, bağlı bulunan 3 müşteri sistemi yerine, 100 alt tipin uygulanması ve sürdürülmesine büyük ölçüde çarpık olacaktır IMotion. Bu, tüm bakım zorluklarımızı, arayüzü kullanan 3 yerden değil, bu 100 alt tipin bakımına kaydırdı. Koddaki 3 yeri az sayıda ya da hiç "dolaylı efferent kuplajı" olmadan (doğrudan bağımlılık değil, bir arayüz aracılığıyla bağımlılıklarda olduğu gibi), doğrudan bir bağımlılık olmadan), önemli bir şey yok: 100 adet alt tipi yeri "dolaylı efferent kuplajları" ile doldurma , oldukça büyük bir anlaşma *.

* Bu bağlamda "efferent coupling" tanımını bir uygulama perspektifinden mahvetmenin garip ve yanlış olduğunu fark ettim, sadece hem arayüz hem de yüz alt tipin ilgili uygulamalarında ortaya çıkan bakım karmaşıklığını tanımlamak için daha iyi bir yol bulamadım. Değişmeli.

Bu yüzden zorlamam gerekti ama ben biraz daha pragmatik olmaya çalışmayı ve tüm "saf arayüz" fikrini gevşetmeyi önerdik. IMotionZengin çeşitlilikte uygulamaları olan bir fayda göremediğimiz sürece, tamamen soyut ve vatansız gibi bir şey yapmamın hiçbir anlamı yoktu . Bizim durumumuzda, için IMotionbiz yaptığımız gibi, uygulamaların zengin bir çeşitlilik aslında oldukça bakım kabus dönüşeceğini olmasını istiyoruz çeşitli. Bunun yerine, değişen müşteri gereksinimlerine karşı gerçekten iyi olan tek bir hareket uygulaması yapmaya çalışıyoruz ve çoğu uygulayıcıyı IMotionaynı uygulamayı kullanmaya zorlayarak , aynı uygulamacığı kullanmaya zorluyoruz. t yinelenen hedefler.

Böylece arayüzler Behaviors, bir varlık ile ilişkili olarak daha geniş bir hale geldi . IMotionbasitçe bir Motion"bileşen" haline gelirdi ("bileşen" i tanımladığımız şekli, COM'dan uzak olanı, "tam" bir varlık oluşturan bir parçanın normal tanımına daha yakın olan bir yere değiştirdim).

Bunun yerine:

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

Bunu daha böyle bir şeye çevirdik:

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

private:
    Matrix transformation;
    ...
};

Bu, soyuttan somutluğa geri kaymaya başlamak için bağımlılık inversiyon ilkesinin bariz bir ihlalidir, ancak bana göre böyle bir soyutlama seviyesi, ancak gelecekte makul bir şüphenin ötesinde gerçek bir ihtiyaç öngörebilirsek, Böyle bir esneklik için, tamamen "ne olursa olsun" (muhtemelen bir tasarım değişikliği gerektirecek olan) kullanıcı deneyiminden tamamen ayrı senaryoların gülünç verilmesi.

Böylece bu tasarıma gelişmeye başladık. QueryInterfacedaha çok oldu QueryBehavior. Ayrıca, burada miras kullanımı anlamsız görünmeye başladı. Bunun yerine kompozisyon kullandık. Nesneler, kullanılabilirliği sorgulanıp çalışma zamanında enjekte edilebilecek bir bileşen koleksiyonuna dönüştürüldü.

görüntü tanımını buraya girin

Bazı artılar:

  • Bizim durumumuzda bakımını yapmak, önceki saf arayüz COM tarzı sisteme göre çok daha kolaydı. Gereksinimlerdeki bir değişiklik veya iş akışı şikayetleri gibi öngörülemeyen sürprizler Motion, örneğin çok fazla merkezi ve açık bir uygulama ile daha kolay bir şekilde yerleştirilebilir , örneğin, yüz alt tipe dağılmamıştır.
  • Gerçekte ihtiyaç duyduğumuz türden yepyeni bir esneklik düzeyi sağladı. Önceki sistemimizde kalıtım, statik bir ilişki oluşturduğundan, C ++ 'da derleme zamanında yeni varlıkları etkili bir şekilde tanımlayabildik. Bunu betik dilinden yapamadık, örneğin, Kompozisyon yaklaşımıyla, çalışma zamanında yeni varlıkları yalnızca bileşenlere ekleyerek ve bir listeye ekleyerek bir araya getirebiliriz. Bir "varlık", üzerine anında ihtiyaç duyduğumuz her şeyin bir kolajını bir araya getirebileceğimiz boş bir tuvale dönüştü, ilgili sistemler sonuç olarak bu varlıkları otomatik olarak tanıdı ve işledi.

Bazı eksileri:

  • Verimlilik departmanında hala zor zamanlar geçirdik ve performans açısından kritik alanlarda sürdürülebilirlik yapıyorduk. Her bir sistem, hepsi arasında tekrar tekrar dolaşmaktan ve mevcut olanı kontrol etmekten kaçınmak için bu davranışları sağlayan varlıkların bileşenlerini önbelleğe almak istemeye devam edecektir. Performans talep eden her sistem bunu çok az farklı bir şekilde yapardı ve bu önbelleğe alınmış listeyi ve muhtemelen bir veri arama işleminde (frustum ayırma veya raytura gibi bir tür arama yapıldıysa) bir veri yapısını güncellememekte farklı bir hatalar dizisine yatkındı. karanlık sahne değişikliği olayı, örneğin
  • Hala bu tuhaf küçük davranışsal, basit nesnelerle ilgili parmağımı sokamadığım garip ve karmaşık bir şey vardı. Bazen gerekli olan bu "davranış" nesneleri arasındaki etkileşimlerle başa çıkmak için hala birçok olay ortaya koyduk ve sonuç çok merkezi olmayan bir koddu. Her küçük nesnenin doğruluğunu test etmesi kolaydı ve ayrı ayrı alındığında çoğu zaman kusursuzca doğru oldu. Yine de, küçük köylerden oluşan büyük bir ekosistemi korumaya çalışıyoruz ve hepsinin bireysel olarak yaptıkları ve bir bütün olarak ekleyebilecekleri şeyler hakkında düşünmeye çalışıyor gibiydik. C tarzı 80'lerin kod temeli, kesinlikle bir bakım kabusu olan epik, aşırı nüfuslu bir megalopolis gibiydi.
  • Soyutlama eksikliği ile esneklik kaybı, ancak bunun için gerçekten gerçek bir ihtiyaçla karşılaşmadığımız bir alanda, çok pratik bir aleyhte (kesinlikle en azından teorik olarak olsa da).
  • ABI uyumluluğunu korumak her zaman zordu ve bu, sadece "davranış" ile ilişkili kararlı bir arayüz değil, kararlı veri gerektirerek daha da zorlaştı. Bununla birlikte, kolayca yeni davranışlar ekleyebiliriz ve eğer bir devlet değişikliğine ihtiyaç duyulursa mevcut davranışları basitçe kullanım dışı bırakabilirdik ve bu, sürüm kaygılarını ele almak için alt tip seviyesindeki arayüzlerin altında geri dönüşler yapmaktan daha kolaydı.

Ortaya çıkan olaylardan biri, bu davranışsal bileşenlerle ilgili soyutlamayı kaybettiğimizden daha fazlasına sahip olmamızdı. Örneğin, soyut bir IRenderablebileşen yerine, somut Meshveya PointSpritesbileşenli bir nesne eklerdik. Render sistemi nasıl oluşturulacağını Meshve PointSpritesbileşenlerini bilir ve bu bileşenleri sağlayan ve bunları çizen varlıkları bulur. Diğer zamanlarda, bizzat SceneLabelgörmemiz gerektiğini keşfettiklerimiz gibi çeşitli tariflere sahip olduk ve SceneLabelbu gibi durumlarda ilgili varlıklara (muhtemelen a'ya ek olarak Mesh) bağlanabilirdik . Rendering sistemi uygulaması daha sonra, bunları sağlayan varlıkları nasıl oluşturacağını bilmek için güncellenecekti ve bu, yapılması oldukça kolay bir değişiklikti.

Bu durumda, bileşenlerden oluşan bir işletme daha sonra başka bir işletmeye bileşen olarak da kullanılabilir. Lego bloklarını asarak bu şekilde işleri inşa ederdik.

ECS: Sistemler ve Ham Veri Bileşenleri

Bu son sistem, kendi başıma yaptığım kadardı ve hala COM ile başa basıyorduk. Bir varlık-bileşen sistemi olmak istiyor gibiydi ama o zamanlar buna aşina değildim. Mimari oyun için AAA oyun motorlarına bakmam gereken zamanlar alanımı doyan COM tarzı örneklere bakıyordum. Sonunda bunu yapmaya başladım.

Kaçırdığım birkaç önemli fikirdi:

  1. "Bileşenleri" işlemek için "sistemlerin" biçimlendirilmesi.
  2. "Bileşenler", birlikte daha büyük bir nesnede oluşan davranışsal nesneler yerine ham verilerdir.
  3. Bir bileşen koleksiyonuyla ilişkilendirilen kesin bir kimlikten başka bir şey olmayan varlıklar.

Sonunda o şirketten ayrıldım ve ECS üzerinde bir indy olarak çalışmaya başladım (tasarruflarımı sürdürürken hala üzerinde çalışıyorum) ve yönetilmesi en kolay sistemdi.

ECS yaklaşımında dikkatimi çeken, hala mücadele ettiğim sorunları çözdüğü idi. Benim için en önemlisi, karmaşık etkileşimli ufacık küçük köyler yerine, sağlıklı büyüklükteki "şehirleri" yönettiğimizi hissettim. Nüfusunda etkin bir şekilde yönetilemeyecek kadar büyük, yekpare bir "megalopolis" kadar sürdürmek zor değildi, ancak birbirleriyle etkileşime giren küçük küçük köylerle dolu bir dünya kadar kaotik değildi; Aralarında bir kabus grafik oluşturdu. ECS, bir karmaşıklık sistemi olan, "büyük ölçekli bir megalopolis" değil, büyük ölçekli bir "şehir" gibi, bir renderleme sistemi gibi tüm karmaşıklığı damıtıyordu.

Ham veri haline gelen bileşenler ilk başta bana çok garip geldi, çünkü OOP'un temel bilgi gizleme ilkesini bile bozuyordu. O, kapsülleme ve bilgi gizleme gerektiren değişmezleri sürdürme kabiliyeti olan OOP hakkında canım tuttuğum en büyük değerlerden birini zorluyordu. Ancak, bu tür bir mantık yerine bu verileri dönüştüren, sadece bir düzine kadar geniş sistemde ne olduğu ve hızlı bir şekilde ortaya çıkmaya başladığı için, kaygısız hale gelmeye başladı. Sistemlerin verilere erişen işlevsellik ve uygulamaları sağladıkları, bileşenlerin verileri sağladıkları ve varlıklar bileşenleri sağladıkları dışında, hala OOP tarzı bir şekilde düşünmeye meyilliyim.

Verileri geniş geçişlerde dönüştüren bir avuç büyük hacimli sistem olduğunda, sistemin neden olduğu yan etkiler hakkında akıl yürütmek, sezgisel olarak daha da kolaylaştı . Sistem çok daha "düz" oldu, çağrı yığınlarım her iş parçacığı için her zamankinden daha sığ oldu. Sistemi o derece daha fazla düşünebilir ve tuhaf sürprizlerle karşılaşmazdım.

Aynı şekilde, performansı sorgulayan alanları bile bu soruları ortadan kaldırmak için basitleştirmiştir. "Sistem" fikri çok biçimsel hale geldiğinden, bir sistem ilgilendiği bileşenlere abone olabilir ve bu kriterleri karşılayan önbelleklenmiş bir liste olarak kullanılabilir. Her biri, bu önbellek optimizasyonunu yönetmek zorunda değildi, tek bir yerde merkezileşti.

Bazı artılar:

  • Beklenmeyen ihtiyaçlarla karşılaşırken, bir tasarım köşesinde hapsolmuş hissetmeden, kariyerimde karşılaştığım hemen hemen her büyük mimari sorunu çözmüş gibiyim.

Bazı eksileri:

  • Bazen kafamın etrafını sarmakta hala zorlanıyorum ve bu, insanların tam olarak ne anlama geldiği ve nasıl yapılacağı hakkında tartıştıkları oyun endüstrisinde bile en olgun veya köklü paradigma değil. Bu kesinlikle birlikte çalıştığım eski ekiple yapabileceğim bir şey değildi, ki bu, COM tarzı zihniyete ya da 1980'lerin orijinal kod tabanının C tarzı zihniyetine bağlı olan üyelerden oluşuyordu. Kafamın karıştığı yer bazen bileşenler arasındaki grafik tarzı ilişkilerinin nasıl modelleneceği gibidir, ancak daha sonra her zaman korkunç bir sonuç çıkmayan bir bileşen buldum; bileşen, diğerine ebeveyn olarak bağlıdır ve sistem aynı yinelemeli hareket hesaplamalarını tekrar tekrar yapmaktan kaçınmak için memoizasyonu kullanır ",
  • ABI hala zor, ancak şimdiye kadar saf arayüz yaklaşımından daha kolay olduğunu söylemeye bile çalışıyorum. Bu, zihniyetteki bir değişimdir: veri kararlılığı, arabirim kararlılığı yerine ABI için tek odak noktası haline gelir ve bazı durumlarda veri kararlılığını elde etmek, arabirim kararlılığından daha kolaydır (örneğin: yeni bir parametreye ihtiyaç duyduğu için bir işlevi değiştirme cazibesi yoktur). Bu tür şeyler ABI’yi bozmayan kaba sistem uygulamalarının içinde olur).

görüntü tanımını buraya girin

Bununla birlikte, oyun motorlarında ortak olan Bileşen-Varlık-Sistem mimarisini kullanarak uygulamalar oluşturmak makul olur mu?

Her neyse, kesinlikle "evet" derdim, kişisel VFX örneğim güçlü bir adaydı. Ama bu hala oyunun ihtiyaçlarına oldukça benzer.

Oyun motorlarının kaygılarından tamamen bağımsız olarak daha uzak alanlarda uygulamaya koymadım (VFX oldukça benzer), ancak sanırım ECS yaklaşımı için çok daha fazla alan iyi adaylar gibi görünüyor. Belki bir GUI sistemi bile biri için uygun olabilir, ama ben hala orada daha fazla OOP yaklaşımı kullanıyorum (ancak Qt'den farklı olarak derin miras olmadan).

Yaygın olarak keşfedilmemiş bir bölgedir, ancak varlıklarınız ne zaman zengin bir “özellikler” kombinasyonundan (ve ne tür bir değişime tabi olmak için sundukları özelliklerin tam bir kombinasyonundan) oluşabileceği ve genel olarak bir avuç kaynağınız olduğu zaman bana uygun görünüyor. Gerekli özelliklere sahip varlıkları işleyen sistemler.

Bu durumlarda, birden fazla kalıtım ya da kavram emülasyonu gibi bir şeyi kullanmaya istekli olacağınız herhangi bir senaryoda çok pratik bir alternatif haline gelir, yalnızca derin bir miras hiyerarşisinde yüzlerce veya daha fazla kombinasyon üretmek için yüzlerce veya daha fazla kombinasyon üretmek için Belirli bir arabirim kombinasyonunu uygulayan, ancak sistemlerinizin sayısının az olduğu (düzinelerce, örneğin) düz bir hiyerarşideki sınıfların listesi.

Bu durumlarda, kod tabanının karmaşıklığı, tür kombinasyonlarının sayısı yerine sistem sayısıyla daha orantılı olarak hissedilmeye başlar, çünkü her bir tür artık yalnızca ham verilerden başka bir şey olmayan bileşenleri oluşturan bir varlıktır. GUI sistemleri, doğal olarak, diğer baz türlerinden veya arayüzlerinden birleştirilmiş yüzlerce olası widget türüne sahip olabileceği bu tür özelliklere uyuyor, ancak bunları işlemek için yalnızca bir avuç sistem (düzen sistemi, görüntü oluşturma sistemi, vb.). Eğer bir GUI ECS kullanıyorsa, devralma arayüzleri veya temel sınıfları olan yüzlerce farklı nesne tipi yerine bu fonksiyonların bir avuç tarafından sağlanmış olması durumunda, sistemin doğruluğunu düşünmek çok daha kolay olacaktır. Bir GUI sistemi ECS kullanıyorsa, widget'ların işlevselliği olmaz, yalnızca veriler olur. Sadece pencere öğesi varlıklarını işleyen sistemlerin bir kısmı işlevselliğe sahiptir. Bir widget için nasıl geçersiz kılınabilir olayların üstesinden gelineceği, ancak şu ana kadarki sınırlı deneyimime dayanarak, bu tür bir mantığın belirli bir sisteme merkezi olarak belirli bir sisteme aktarılamadığı bir durum bulamadım. ön görüşte, beklediğimden çok daha zarif bir çözüm üretti.

Maden cankurtaran olduğu için daha çok alanda kullanıldığını görmek isterim. Elbette, tasarımınız bu şekilde bozulmazsa, var olan bileşenleri bir araya getiren bileşenlerden, bu bileşenleri işleyen kaba sistemlere, ancak bu tür bir modele doğal olarak uyuyorlarsa, şu ana kadar karşılaştığım en harika şey uygun değildir. .


1) Örnek VFX programınız bir kullanıcının bakış açısından ne yaptı? 2) Şu anda hangi ECS projesi üzerinde çalışıyorsunuz? ♥ Bunu yazdığın için teşekkürler! ♥
pup,

1
Çok ayrıntılı açıklama - teşekkür ederim. ECS'nin oyunların ötesinde ne kadar geçerli olduğuna ilişkin olarak yaptığınız aynı sonuçların birçoğuna geldiğimi hissediyorum; benim durumumda özellikle karmaşık GUI'ler. Genelde yapılanın tahılın karşısına gitmek için gerçekten çok garip hissettiriyor (derin miras hiyerarşileri özellikle UI çerçevelerinde öne çıkıyor), ancak bu yaklaşımı daha etkili bulan başkalarını görmeyi duyuyor.
Danny Yaroslavski

1
Bu harika yazı için teşekkür ederim. Bileşen tabanlı bir GUI için Unity3d'nin UGUI'sine bakmanızı tavsiye ederim. CocoaTouch gibi kalıtımsal özelliklere kıyasla inanılmaz derecede esnek ve genişletilebilir.
Ivan Mir

16

Oyun motorları için Bileşen-Varlık-Sistem mimarisi, oyun yazılımının doğası ve benzersiz özellikleri ve kalite gereklilikleri nedeniyle oyunlar için çalışır. Örneğin, varlıklar oyundaki şeyleri hedeflemek ve kullanmak için büyük ölçüde farklı olabilecek, ancak sistem tarafından tek tip bir şekilde yapılması, güncellenmesi veya seri hale getirilmesi / seri hale getirilmesi gereken üniform bir yolla ele alınmalarını sağlar. Bu mimariye bir bileşen model ekleyerek, düşük kodlu birleştirme ile gerektiğinde daha fazla özellik ve işlevsellik eklerken, basit bir çekirdek yapısını korumalarına izin verirsiniz. CAD uygulamaları, A / V kodekleri gibi bu tasarımın özelliklerinden yararlanabilecek çeşitli yazılım sistemleri vardır,

TL; DR - Tasarım kalıpları sadece sorunlu alanın tasarım üzerine getirdiği özellik ve dezavantajlara uygun olduğu durumlarda işe yarar.


8

Sorun alanı kesinlikle buna uygunsa, kesinlikle.

Şu andaki çalışmam, bir dizi çalışma zamanı faktörüne bağlı olarak çeşitli yetenekleri desteklemesi gereken bir uygulamayı içeriyor. Tüm bu yetenekleri ayırmak ve izolasyonda genişletilebilirlik ve test edilebilirlik sağlamak için bileşen tabanlı varlıkları kullanmak bizim için pastoral oldu.

düzenleme: Çalışmam, tescilli donanıma (C #) bağlanabilirliği içerir. Donanımın hangi faktöre bağlı olduğuna, hangi donanım yazılımının yüklü olduğuna, müşterinin hangi düzeyde hizmet aldığı vb. Bağlı olarak cihaza farklı işlevler sağlamamız gerekir. Aynı arayüze sahip bazı özellikler bile, cihazın hangi sürümde olduğuna bağlı olarak farklı uygulamalara sahiptir.

Buradaki önceki kod tabanları pek çoğu uygulanmayan çok geniş arayüzlere sahipti. Bazıları daha sonra statik olarak bir hayvan sınıfında oluşan birçok ince arayüze sahipti. Bazı basitçe dize -> dize sözlükleri onu modellemek için kullandı. (herkesin daha iyisini yapabileceğini düşünen birçok bölümümüz var)

Bunların hepsinin kendi eksiklikleri var. Geniş arayüzler etkili bir şekilde alay etmek / test etmek için bir buçuk acıdır. Yeni özellikler eklemek, genel arayüzü (ve mevcut tüm uygulamaları) değiştirmek anlamına gelir. Pek çok ince arayüz çok çirkin tüketen kodlara neden oldu, ancak büyük bir şişman nesne testinden geçtikten sonra hala acı çekti. Ayrıca, ince arayüzler bağımlılıklarını iyi yönetemedi. Yaylı sözlükler olağan ayrıştırma ve varoluş sorunlarına ve performans, okunabilirlik ve sürdürülebilirlik cehennem deliklerine sahiptir.

Şu an kullandığımız şey, bileşenleri çalışma zamanı bilgisine dayanarak keşfedilen ve oluşturulan çok ince bir varlık. Bağımlılıklar bildirimsel olarak yapılır ve çekirdek bileşen çerçevesi tarafından otomatik olarak çözülür. Bileşenlerin kendileri, doğrudan bağımlılıklarıyla çalıştıkları ve bağımlılıkları eksik olan sorunlar, bağımlılığın ilk kullanımından ziyade erken ve tek bir yerde bulundukları için yalıtılmış olarak test edilebilir. Yeni (veya test) bileşenler düşürülebilir ve mevcut hiçbir kod bundan etkilenmez. Tüketiciler, bileşenden bir arabirim isterler; bu nedenle çeşitli uygulamaları (ve uygulamaların çalışma zamanı verilerine nasıl eşleştirildiğini) göreceli bir özgürlükle çözmekte özgürüz.

Nesnenin ve arayüzlerin kompozisyonunun ortak bileşenlerin bazı (çok çeşitli) alt kümelerini içerebildiği böyle bir durum için, çok iyi çalışır .


1
İzin verildiğini varsayarak, mevcut işiniz hakkında daha fazla ayrıntı verebilir misiniz? Yaptığınız şey için CES'in ne kadar pastoral olduğunu bilmek istiyorum.
Andrew De Andrade

Deneyiminizle ilgili herhangi bir makale, makale veya blog var mı? ayrıca, onun hakkında daha fazla teknik ayrıntıya sahip olmak istiyorum :)
user1778770

@ user1778770 - herkese açık değil, hayır. Ne tür sorularınız vardı?
Telastyn,

Peki, basit bir şeyle başlayalım, konseptiniz tüm uygulama yığınını kapsıyor mu (örneğin, işletmeden işletmeye)? ya da sadece bir tek kullanımlık kasanın tek bir katmanı?
user1778770

@ user1778770 - benim uygulamada, varlıklar / bileşenler bir katmanda var. Farklı katmanlar farklı katmanlar halinde olabilirler, fakat genellikle 1: 1 değildirler (veya katmanlar fayda sağlamıyor).
Telastyn,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.