Prestazioni a collo di bottiglia in ECS

5

Ho cercato di creare un sistema di componenti di entità. Fondamentalmente, un'entità è solo un ID avvolto attorno a una struttura, i componenti sono dati appartenenti a quell'entità (e si riferiscono a detto id), e i sistemi sono il codice. Entità e componenti sono tutti memorizzati all'interno di array, per consentire un'iterazione veloce su ciascuno.

Ad esempio, un'entità potrebbe avere un componente Massa, Posizione e Velocità. Un GravitySystem prenderà in considerazione queste tre componenti, calcolerà una certa velocità (basata sulla massa) e la aggiungerà al componente Posizione.

Il mio problema è, cosa succede quando un'entità viene rimossa dal centro di un array? Un'opzione è di avere l'ultimo elemento delle posizioni di scambio dell'array con l'entità che è stata appena rimossa, in modo che l'array rimanga impacchettato. Il rovescio della medaglia è che perdo la possibilità di fare riferimento a ciascun elemento con lo stesso numero di indice, cioè l'ID 5 dell'entità è nell'indice 5 e ogni componente che appartiene a quell'entità si trova anch'esso nell'indice 5 all'interno dei propri array.

Una soluzione sarebbe chiedere semplicemente se l'entità [i] è "attiva" prima di ogni iterazione. Qualcosa di simile,

void gravitySystem(entityList[], massList[], velocityList[], positionList[]) 
{
    for(int i = 0; i < 100; ++i) {
       if(entityList[i].isAlive == 1) {
         velocityList[i] += 9.81 * massList[i];
         positionList[i] += velocityList[i];
       }
       else {
         printf("The entity is dead, Jim.\n");
       }
     }
}

Il mio problema con questa soluzione è che se dovessi avere un enorme elenco di entità, ad esempio 4M, durante questo ciclo, allora l'istruzione if (entityList [i] .isAlive == 1) avrebbe un impatto sulle prestazioni . C'è un'altra soluzione che rimuoverebbe questo collo di bottiglia? Forse uno che manterrebbe la matrice piacevole e confezionata senza "buchi" in essa?

    
posta Daniel Martin 23.12.2014 - 19:06
fonte

2 risposte

2

Una possibile soluzione sarebbe quella di sostituire l'entità con una nuova entità senza componenti. Con il setup che hai, sembra che sarebbe solo questione di lasciare l'entità lì e rimuovere tutti i componenti che fanno riferimento all'ID dell'entità. Quindi, i tuoi sistemi dovrebbero naturalmente saltare quell'entità dal momento che non ha i componenti che richiedono.

Allo stesso tempo, è possibile aggiungere l'indice a un elenco di indici di entità "riciclabili". Quando crei nuove entità, se c'è qualcosa in quell'elenco, sposta un indice fuori dall'elenco e usa l'entità a quell'indice invece di posizionarlo alla fine. A questo punto non avrà componenti, quindi puoi dargli i nuovi componenti, rendendoli effettivamente una nuova entità.

Si presume che i sistemi salteranno le entità che non hanno i componenti richiesti. Idealmente potresti scrivere qualcosa come questo (c ++):

class GravitySystem : public System<MassComponent, PositionComponent, VelocityComponent> {
    public:
    GravitySystem() {}

    void logic(Entity& e) {
        auto mass = e.get<MassComponent>();
        auto pos = e.get<PositionComponent>();
        auto vel = e.get<VelocityComponent>();
        vel->y += 9.81 * mass->value;
        pos->x += vel->x;
        pos->y += vel->y;
    }
};

E poi nel tuo ciclo di gioco scrivi qualcosa come gravitySystem.process(entities) , e itera su ogni entità e applica la logica su ogni entità che ha i componenti richiesti. Quindi non devi preoccuparti degli ID a meno che non ti servano per qualcos'altro. Dai un'occhiata a darkf / microecs per un esempio.

    
risposta data 23.12.2014 - 21:02
fonte
0

La soluzione semplice consiste nell'utilizzare un elenco gratuito e, naturalmente, rimuovere tutti i componenti quando si richiede di rimuovere un'entità. Riutilizza la memoria di un'entità come un indice per l'entità libera next quando viene rimossa dall'elenco (senza rimuoverla dall'array o spostarla in giro). Questo diagramma dovrebbe chiarire l'idea:

Esempiodicodice:

structEntity{unionData{...intnext_free;};Datadata;};structEcs{...vector<Entity>entities;intfree_entity;//setto-1initially};intEcs::insert_entity(constEntity&entity){if(free_entity!=-1){//Ifthere'safreeentityindexavailable,//popitfromthefreelistandoverwritethe//entityatthatindex.constintindex=free_entity;free_entity=entities[free_entity].data.next_free;entities[index]=entity;returnindex;}else{//Otherwiseinsertanewentity.entities.push_back(entity);returnentities.size()-1;}}voidEcs::erase_entity(intentity_index){//Pushtheentityindextothefreelist.entities[entity_index].data.next_free=free_entity;free_entity=entity_index;}

Ciòmanterràituoiindiciinvalidi,consentiràunrapidorecuperodeglispazivuotiemanterràiltuoinserimentoerimozionesporcoabassocostooperazioniatempocostanteetuttosenzacrearealcunanuovastrutturadatioqualcosadelgenere.

Ovviamenteognivoltachesidisponediunarraycon"buchi", può iniziare a sprecare memoria se ci sono molti spazi vuoti in cui il cliente ha rimosso un sacco di cose e non si è mai preso la briga di inserire qualcosa di nuovo. Un modo per mitigare quello è usare una sequenza di accesso casuale composta da blocchi contenenti, diciamo, 16 entità ciascuna. Se un blocco diventa completamente vuoto, è possibile liberare la sua memoria e sostituirla con un puntatore nullo, per essere riallocata in un secondo momento se il client vuole mai reclamare l'intervallo di indici rappresentato dal blocco precedentemente rappresentato. Questo ha lo svantaggio di un accesso casuale più lento (un po 'più aritmetico per accedere all'elemento nth ), ma fa un lavoro migliore per liberare memoria quando le persone rimuovono gli elementi da esso. È anche compatibile con l'approccio alla lista gratuita.

    
risposta data 12.12.2017 - 21:23
fonte

Leggi altre domande sui tag