define('DISALLOW_FILE_EDIT', true);
define('DISALLOW_FILE_MODS', true);
Da quando è uscito Crysis (ma anche da prima) continuo a leggere questa frase:
“Crysis è programmato male perchè sul mio uber-pc scatta con tutto al massimo“.
Cosa possiamo dire di Crysis? Probabilmente che le stime di crescita della potenza dei PC sono state troppo ottimistiche.
Possiamo dire che sia stato programmato male? No. Perchè no? Perchè programmare bene non vuol dire programmare in modo che il gioco sia veloce. Quello è solo un punto, probabilmente nemmeno il più importante.
Vediamo cos’altro è importante. Un gioco è ben programmato se segue le regole della buona programmazione che valgono per ogni software:
– Codice documentato;
– Accoppiamento tra i moduli al minimo possibile;
– Sviluppo il più possibile Data-Driven;
– Chiarezza;
– Solidità;
– Ottimizzazione.
Prima si affronta il design, poi si scrive il codice nella maniera migliore possibile in modo che rispetti le richieste e infine, se il collo di bottiglia si trova nei nostri listati, si procede con l’ottimizzazione.
Consideriamo in dettaglio tutti i punti.
CODICE COMMENTATO
Se il codice non è commentato e la documentazione non viene scritta, qualora il suo autore dovesse abbandonare l’azienda, con lui andrà perso anche il suo know-how. Questo non deve succedere, perché una qualunque modifica al software nella parte di sua competenza sarebbe estremamente complessa e di certo genererebbe grossi bug di arduo tracciamento. Il codice va dunque commentato e la documentazione redatta. Crysis soddisfa questo punto? Siccome l’engine viene venduto, possiamo desumere che venga venduto con documentazione annessa. In passato ho lavorato con Renderware e tutto era documentato, sia nel codice sia nella documentazione allegata. Non vedo perchè Crysis avrebbe dovuto toppare su questo punto, ma siccome non possiamo saperlo, lasciamolo lì dov’è.
ACCOPPIAMENTO TRA I MODULI AL MINIMO POSSIBILE
Supponiamo che la sezione dell’input ad un certo punto voglia usare un pezzo del gioco, che gestisce magari il suono delle collisioni tra due oggetti.
E’ così creato un accoppiamento tra due moduli. Ciò può portare a mille problemi: ad esempio se modifico la collisione, di colpo l’input potrebbe non funzionare più. Per tale ragione si cerca di tenere gli accoppiamenti al minimo, limitandoli a ciò che è logicamente collegato ed ai tipi base.
Un esempio estremo: se ho un dato condiviso da più moduli e uno di questi lo modifica, magari il programmatore nemmeno immagina che questo viene utilizzato da qualcos’altro, quindi questo qualcos’altro non funzionerà più. Errori del genere portano via giorni. Crysis è a posto o ha tanti accoppiamenti? Se leggiamo le feature, notiamo che non può essere che con pochi accoppiamenti: una tale struttura permette un fortissimo riutilizzo dei componenti (giacché vivono in maniera autonoma). Già la sola integrazione perfetta con un ambiente di authoring ci fa capire che i moduli sono distinti.
SVILUPPO IL PIU’ POSSIBILE DATA-DRIVEN
Ed è qui che i fanatici dell’ottimizzazione gridano: una gestione data driven è più lenta di una gestione code-driven. Questo perché il dato va letto, interpretato e gestito, mentre se piazzato direttamente nel codice, questo è già pronto quando il gioco si compila.
Ad esempio: di che colore vogliamo le scritte sullo schermo? Se mettiamo il colore nel codice, questo sarà nell’eseguibile, pronto ad essere assegnato quando richiesto. Se lo mettiamo in un XML, questo andrà letto e interpretato, per poi prendere il dato e metterlo nella variabile dopo, magari, una conversione di tipo.
Quindi, perchè si fa? In primo luogo perché se la lettura del file si realizza in maniera oculata (ad esempio con un parser SAX invece che DOM per l’XML, oppure con un parsing in memoria per un binario), allora allungherà i tempi di startup di un tempo risibile, e inoltre in secondo luogo renderà il gioco immensamente più modificabile e tunabile per i designer ed i grafici (oltre che per l’utente). Pensiamo solo alle mesh: cosa succederebbe se il programmatore mettesse tutte le posizioni dei vertici nei sorgenti? Succederebbe che se al grafico una mesh non piace, bisognerebbe cambiare il dato a mano e ricompilare; ovviamente non ha senso: ha molto più senso che il grafico modifichi la mesh incriminata in 3dStudio Max, la riesporti e la ricarichi in gioco. Oppure pensiamo ad un designer che voglia inserire i nomi dei giocatori in un titolo calcistico, o ad un programmatore che intenda scrivere un nuovo shader.
Insomma, i benefici nei tempi di sviluppo sono talmente grandi che le perdite in fase di inizializzazione vengono del tutto riassorbite. Se consideriamo che il concetto di gioco data-driven risultava già ben presente ai tempi del C64, possiamo facilmente notare quanto sia importante.
Crysis è provvisto di tale approccio? In maniera massiccia: abbiamo editor per gli effetti, editor per gli stati, editor per i menù, editor per il gioco, editor per l’AI, editor per l’audio, editor per tutto
Non c’è niente di hardcodato, tutto è data driven, per la massima modificabilità senza dover ricompilare alcunché. Questo ne conferma anche la modularità: il fatto che possiamo mettere vari moduli per creare degli effetti ci fa intuire che essi siano ben distinti e non connessi tra loro.
CHIAREZZA
Quando si scrive un programma, lo si deve fare pensando che saranno degli occhi umani a leggerlo. Il compilatore non ha molto interesse nella chiarezza: lui prende tutto quello che la grammatica del linguaggio accetta.
Se iniziamo a indentare il codice a caso e a dare alle variabili nomi inconsistenti (come ad esempio “deltaT” a variabili che indicano il tempo assoluto), allora andremo incontro a sessioni di debug molto faticose. Parimenti, se abusiamo del preprocessore dovremo affrontare simili avversità.
Crysis soddisfa questo requisito? Boh, spero che i lead controllino il codice, ma immagino di sì 
SOLIDITA’
Un software è solido quando, di fronte a tutti gli usi previsti dall’interfaccia d’uso, dagli input e dagli output, rimane funzionante e operativo nei tempi richiesti. Se di fronte ad una sequenza di input considerata corretta, il software entra in deadlock o crasha, allora non può essere ritenuto solido.
La solidità viene valutata con test mirati a stressare il sistema, come ad esempio saturare l’input, oppure lasciare il gioco a girare per giorni e giorni.
Come si ottiene la stabilità? In primo luogo con la disciplina: tutte le variabili allocate vanno prima o poi (quando serve) disallocate. Tutte le risorse hardware vanno rilasciate quando non servono più e tutto il sistema deve pensare di avere una serie di check per valutare che queste condizioni siano rispettate. In genere un gioco stabile è un gioco programmato con attenzione, come tutti i software e comunque per qualunque opera di ingegneria. Un ponte non crolla se è stato progettato con attenzione, giusto?
Quando, d’altro canto un software è instabile? Quando di fronte ad una precisa sequenza di operazioni considerate “safe” il programma inaspettatamente si blocca (nel caso in cui un software funzioni in multithreading, questa sequenza potrebbe non essere sempre riproducibile con facilità, rendendo il debug un autentico bagno di sangue).
Per evitare tali eventualità, si cerca di tenere il codice il più semplice possibile, tralasciando voli pindarici o ottimizzazioni spinte sulla macchina (tanto quelle ottimizzazioni il compilatore le sa fare meglio di noi, quindi perché preoccuparci?).
In Crysis è così? Non possiamo saperlo. Ma il gioco è stabile? Per quanto ho visto, funziona su una pletora di configurazioni senza crashare per molte ore di fila. Un miracolo, su PC.
OTTIMIZZAZIONE
Quando si trova un collo di bottiglia, si cerca di eliminarlo ottimizzando. Già in passato abbiamo visto che l’ottimizzazione sull’assembly senza cambiare algoritmo porta a guadagni tanto risibili da non avere senso.
Sì, amighisti, sì, lo so che sul vostro Amiga girava Lionhead: anche lì non pensiate che l’ottimizzazione sia stata fondamentalmente sulla macchina, bensì è stata effettuata su algoritmi custom e appositamente sviluppati per l’uso richiesto. Sono quelle le cose che consentono di ottimizzare tanto e davvero.
Vediamo: se c’è un collo di bottiglia sul processing dei pixel shader che si fa? Dove si controlla: nei conti degli shader o nei campionamenti delle texture? Se nei campionamenti, la scelta giusta consiste nell’ottimizzare le dimensioni delle texture: texture piccole per la roba lontana o piccola. Se è nei vertex shader, stessa cosa: sono i conti o il fatto che le mesh hanno una barca di vertici? E così via, per tutti i reparti del gioco.
Per Crysis, proviamo a pensarci: dov’è il collo di bottiglia? Ovviamente sui dati: si vuole mostrare sullo schermo TROPPO. Se vi fosse stata la necessità di andare più veloci, al fine di rendere l’insieme più gestibile, allora si sarebbe proceduto ad una scrematura, adoperando mesh meno dense e così via. Non può essere altro: il solo fatto che tutta quella roba SI MUOVA e sia giocabile in tempo reale ci fa capire che è stato fatto tutto nel modo migliore.
E la faccenda dei multicore? Beh, la solita fregnaccia da redattore (anche se in questo caso è peggio: da redattore esperto di hardware). Ne parliamo prossimamente, ok?
CONCLUSIONI
In definitiva che possiamo dire? Possiamo sostenere che Crysis sia programmato male? No, perché quello che si vede è fatto in maniera eccezionale, mentre quello che non si vede non lo possiamo sapere.
E ora via col flame!
]]>