Ci sono casi in cui globi / singoli sono utili nello sviluppo del gioco? [chiuso]


11

So che avere variabili globali o classi singleton crea casi che possono essere difficili da testare / gestire e sono stato sbalordito nell'usare quei modelli nel codice ma spesso devi spedirli.

Quindi ci sono casi in cui variabili globali o singoli sono effettivamente utili nello sviluppo del gioco?

Risposte:


30

Queste cose possono sempre essere utili . Che sia la soluzione più carina o più sicura è un'altra questione, ma penso che lo sviluppo del gioco implichi un certo grado di pragmatismo.


Molto vicino al mio mantra.
Ólafur Waage,

15

Sicuramente un elenco non esaustivo, ma qui va:

Contro

  • Gestione della vita . È possibile che i singoli e i globali vengano avviati prima dell'inizializzazione dei sistemi chiave (ad es. Heap), a seconda della configurazione. Se mai vuoi demolirli (utile se stai facendo il tracking delle perdite, per esempio) devi stare attento all'ordine di smontaggio o iniziare a entrare in cose come i singoletti di Phoenix . (Vedi il fiasco dell'ordine di inizializzazione statico .)
  • Controllo degli accessi . Il rendering dovrebbe avere solo l'accesso const ai dati di gioco, mentre l'aggiornamento diventa non const? Questo potrebbe essere più difficile da applicare con i singoli e i globali.
  • Stato . Come ha sottolineato Kaj, è più difficile decomporre funzionalmente e trasformare i singoli in modo che funzionino in un'architettura a memoria non condivisa. Ha anche potenziali implicazioni sulle prestazioni per altri tipi di sistemi NUMA (accesso alla memoria che non è locale). I singletoni di solito rappresentano uno stato centralizzato, e quindi sono l'antitesi delle trasformazioni che sono rese più facili dalla purezza.

Pro o contro

  • Concorrenza . In un ambiente concorrente, i singoli possono essere sia una seccatura (è necessario considerare i problemi relativi alla corsa dei dati / rientro) sia una benedizione (è più facile centralizzare e ragionare sul blocco e la gestione delle risorse). L'uso intelligente di cose come l'archiviazione locale dei thread può mitigare un po 'i potenziali problemi, ma di solito non è un problema facile.
  • Codegen (a seconda del compilatore, dell'architettura, ecc.): Per un'implementazione singleton di creazione al primo utilizzo ingenua, potresti valutare un ramo condizionale aggiuntivo per ogni accesso. (Questo può sommarsi.) Più globali utilizzati in una funzione possono gonfiare il pool letterale . Un approccio "struct of all globals" può risparmiare spazio nel pool letterale: solo una voce alla base della struct, e quindi gli offset codificati nelle istruzioni di caricamento. Infine, se stai evitando globalmente e singoli a tutti i costi, devi generalmente usare almeno un po 'di memoria aggiuntiva (registri, stack o heap) per passare puntatori, riferimenti o persino copie in giro.

Professionisti

  • Semplicità . Se sai che i contro sopra elencati non ti influenzano molto (ad esempio, stai lavorando in un ambiente a thread singolo per una singola piattaforma palmare CPU), eviti alcune architetture (come il passaggio di argomenti di cui sopra). Singleton e globali possono essere più facili da capire per i programmatori meno esperti (anche se può essere più facile usarli in modo errato).
  • Gestione della vita (di nuovo). Se si utilizza una "struttura di globuli" o un meccanismo diverso da una richiesta di creazione iniziale, è possibile avere un controllo facile da leggere e dettagliato sull'inizializzazione e sull'ordine di distruzione. Lo automatizzi in una certa misura o lo gestisci manualmente (doverlo gestire può essere un pro o un contro, a seconda del numero di globi / singoli e delle loro interdipendenze).

Usiamo molto una "struct of singletons globali" nei nostri titoli portatili. I titoli per PC e console tendono a fare meno affidamento su di essi; passeremo di più verso un'architettura di messaggistica / guidata dagli eventi. Detto questo, i titoli di PC / console spesso usano ancora un TextureManager centrale; dal momento che di solito avvolge una singola risorsa condivisa (la memoria della trama), questo ha senso per noi.

Se mantieni l'API relativamente pulita, potrebbe non essere troppo difficile refactoring da (o dentro!) Uno schema singleton quando hai bisogno ...


Ottima lista Personalmente trovo solo un valore negativo nei singoli e nei globali a causa dei problemi che derivano dallo stato condiviso (probabilmente mutabile). Non trovo che il problema "codegen" sia un problema; o devi passare i puntatori attraverso i registri o devi fare una sequenza di operazioni per ottenerlo, penso che cambi visivamente il codice rispetto alle dimensioni dei dati, ma la somma sarà più o meno la stessa.
dash-tom-bang,

Sì, per quanto riguarda il codegen, l'unica cosa che abbiamo effettivamente visto fare una differenza significativa stava passando dai singoli globi a una globalizzabile: ha ridotto i pool letterali, e quindi le dimensioni della sezione .text, di diversi percento. Che è una cosa molto bella su piccoli sistemi. =) I vantaggi prestazionali di non colpire il dcache così tanto per i letterali sono stati solo un bel vantaggio (sebbene nella maggior parte dei casi minuscolo). Un altro vantaggio di passare a un tavolo globale era la possibilità di spostarlo facilmente in una sezione che risiedeva in una memoria più veloce ...
magro

4

Possono essere molto utili, specialmente durante la prototipazione o l'implementazione sperimentale, ma in generale preferiamo passare dei riferimenti per strutture come manager, in questo modo almeno hai un po 'di controllo da dove vi si accede. Il problema più grande con i globali e i singoli (secondo me) è che non sono molto compatibili con il multithread e che il codice che li utilizza è molto più difficile da trasferire su memoria non condivisa, come SPU.


2

Direi che il design singleton in sé non è affatto utile. Le variabili globali possono sicuramente essere utili, ma preferirei vederle nascoste dietro un'interfaccia ben scritta in modo da non essere a conoscenza della loro esistenza. Con i single sei decisamente consapevole della loro esistenza.

Uso spesso variabili globali per cose che richiedono l'accesso attraverso un motore. Il mio strumento per le prestazioni è un buon esempio che utilizzo in tutto il motore per chiamare. Le chiamate sono semplici; ProbeRegister (), ProbeHit () e ProbeScoped (). Il loro vero accesso è un po 'più complicato e utilizza variabili globali per alcune delle loro cose.


2

Il problema principale con i globuli e i singoli mal implementati sono gli oscuri bug di costruzione e decostruzione.

Quindi, se lavori con primitive che non hanno questi problemi o sei molto consapevole del problema con un puntatore. Quindi possono essere utilizzati in sicurezza.

I globi hanno il loro posto, come i goto e non dovrebbero essere scartati fuori mano ma usati con cura.

C'è una buona spiegazione nella Guida allo stile di Google C ++


Non sono d'accordo sul fatto che questo sia il problema principale; "non usare i globi" risale oltre i costruttori di esistenza. Direi che ci sono due problemi principali con loro. Innanzitutto, complicano il ragionamento su un programma. Se ho una funzione che accede a un globale, il comportamento di quella funzione dipende non solo dai suoi argomenti, ma anche da tutte le altre funzioni che accedono al globale. C'è molto di più a cui pensare. In secondo luogo, anche se riesci a tenere tutto in testa (non puoi), si traducono comunque in problemi di concorrenza più complicati.

@Joe la parola chiave è "dipendenze". Una funzione che accede ai globi (o singoli o qualsiasi altro stato condiviso) ha dipendenze implicite su tutte queste cose. È molto più facile ragionare su un po 'di codice se tutte le sue dipendenze sono esplicite, che è quello che ottieni quando l'elenco completo delle dipendenze è documentato nell'elenco degli argomenti.
dash-tom-bang,

1

I globuli sono utili durante la prototipazione rapida di un sistema che richiede un certo stato tra le chiamate di funzione. Una volta stabilito che il sistema funziona, sposta lo stato in una classe e trasforma le funzioni in metodi di quella classe.

I singoli sono utili per causare problemi a te stesso e agli altri. Più stato globale introduci, più problemi avrai con correttezza del codice, manutenzione, estensibilità, concorrenza, ecc. Non farlo.


1

Tendo a raccomandare di utilizzare una sorta di contenitore DI / IoC con gestione della durata personalizzata anziché singleton (anche se si utilizza un gestore della durata "a istanza singola"). Almeno quindi è facile scambiare l'implementazione per facilitare i test.


Per quelli di voi che non lo sanno, DI è "Iniezione di dipendenza" e IoC è "Inversione di controllo". Link: martinfowler.com/articles/injection.html (ne avevo letto prima ma non avevo mai visto l'acronimo, quindi mi ci è voluto un po 'di ricerca.) +1 per menzionare questa eccellente trasformazione, però.
magro,


0

I singleton sono un ottimo modo per memorizzare uno stato condiviso in un prototipo iniziale.

Non sono un proiettile d'argento e presentano alcuni problemi, ma sono un modello molto utile per determinati stati di UI / logica.

Ad esempio in iOS, usi i singleton per ottenere [UIApplication sharedApplication], in cocos2d puoi usarlo per ottenere riferimenti a determinati oggetti come [CCNotifications sharedManager] e personalmente di solito inizio con un singleton [Game sharedGame] dove posso stato del negozio condiviso tra molti componenti diversi.


0

Wow, questo è interessante per me, dato che non ho mai avuto personalmente problemi con il modello singleton. Il mio progetto attuale è un motore di gioco C ++ per Nintendo DS e sto implementando molte utility di accesso all'hardware (ad es. VRAM Banks, Wifi, i due motori grafici) come istanze singleton, perché hanno lo scopo di avvolgere C globale funzioni nella libreria sottostante.


La mia domanda sarebbe quindi, perché usare i singleton? Vedo che alla gente piace avvolgere le API con singoli o classi che consistono solo di funzioni statiche, ma perché preoccuparsi della classe? Mi sembra ancora più semplice avere solo le chiamate di funzione (racchiuse come desideri), ma poi interne ad esse accedono allo stato "globale". Ad esempio Log :: GetInstance () -> LogError (...) potrebbe anche essere LogError (...) che accede internamente a qualsiasi stato globale senza richiedere al codice client di conoscerlo.
dash-tom-bang,

0

Solo quando hai un oggetto che ha un solo controller ma è gestito da più moduli.

Come ad esempio l'interfaccia del mouse. O interfaccia con joystick. O lettore musicale. O lettore audio. O lo schermo. O il gestore dei file di salvataggio.


-1

I globuli sono molto più veloci! Quindi si adatterebbe perfettamente per un'applicazione ad alte prestazioni, come un gioco.

I singleton sono un globale migliore, IMO, e quindi lo strumento giusto.

Usalo con parsimonia!


Molto più veloce di cosa ? I globali si troveranno in una pagina di memoria casuale se sono un puntatore, e in una pagina di memoria fissa ma probabilmente distante se sono un valore. Il compilatore non può utilizzare alcuna utile ottimizzazione dello spioncino su di essi. A qualsiasi misura mi venga in mente, i globi sono lenti .

Più veloce di getter e setter e passando riferimenti in giro. Ma non di molto. Aiuta a ridurre le dimensioni, quindi aiuterebbe in alcuni sistemi. Probabilmente ho esagerato quando ho detto "molto", ma sono molto scettico nei confronti di chiunque dica che non dovresti usare qualcosa. Hai solo bisogno di usare il tuo buon senso. Dopotutto, i singoli non sono altro che un membro di classe statico e quelli sono globali.
jacmoe,

Ma per risolvere eventuali problemi di sincronizzazione con i globi sono necessari getter / setter per loro, quindi non è davvero rilevante. Il problema con (e raro beneficio dello) stato globale è che si tratta dello stato globale, non di eventuali preoccupazioni relative alla velocità o (efficacemente) alla sintassi dell'interfaccia.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.