Warcraft Rumble

Dentro Warcraft Rumble: calcolare l'esperienza dei Mini

Blizzard Entertainment

Saluti, fan di Rumble!

Sono Andy Lim, capo programmatore delle caratteristiche dei server di Warcraft Rumble. Il team dei server è responsabile di tutte le cose che ti aspetteresti da un team del server, ossia networking, cloud computing e archiviazione, ma partecipa anche alla creazione di funzionalità di gioco, come la progressione della campagna e gli Incarichi. Vorrei aprire il sipario sul dietro le quinte del gioco e condividere qualche informazione su come viene archiviata l'esperienza dei Mini per poi essere usata per calcolare i livelli di ogni Mini.


Ecco Cassandra

Attenzione! Seguono dettagli tecnici. Potrebbero sembrare un po' difficili, ma non arrenderti.

Iniziamo conoscendo un po' meglio la soluzione di archiviazione che utilizziamo per tenere traccia delle frequenti modifiche ai dati forniti da giocatori e giocatrici quando ci sono molti utenti simultanei: Cassandra. Cassandra è un database open source, famoso, altamente modulato e ripartito, che ci offre il giusto equilibrio tra coerenza e disponibilità dei dati. Per quanto riguarda i dati, Cassandra si occupa di ampi set di dati senza dover forzare uno schema rigido. Abbiamo sviluppato degli strumenti che consentono al team di programmazione di definire le tabelle del nostro database e lo schema delle tabelle in base alle esigenze di ciascuna funzionalità, offrendoci una certa flessibilità nella struttura e nell'organizzazione. Così, possiamo scrivere e convalidare il nostro schema e le nostre query senza problemi.

Cos'è uno schema? Lo schema di un database definisce come i dati vengono organizzati all'interno di un database relazionale, come quello che collega delle tabelle.


Archiviare l'esperienza come in un registro contabile

Cassandra è un programma noto per essere molto veloce nello scrivere i dati ma lento nel leggerli. L'aggiornamento dei dati è spesso un'operazione contemporanea di lettura e scrittura, quindi anche questa seconda fase risulterebbe rallentata. Per aggirare il problema, abbiamo progettato i dati di giocatori e giocatrici in modo che vengano archiviati come in un registro contabile. In un registro contabile, ogni riga scritta conta come una modifica, e quando viene letta, vengono lette tutte le voci e poi vengono fatti dei calcoli su quella riga.

Un buon esempio di un registro contabile è, per esempio, una carta di credito. Ogni transazione è scritta come una singola voce di valore positivo o negativo. Ogni volta che usi la tua carta di credito, c'è una transazione di valore negativo. Ogni volta che la ricarichi, c'è una transazione di valore positivo. Come fai a sapere a quanto ammonta il tuo debito? Devi guardare l'intero registro contabile e aggiungere/sottrarre ogni valore.

Ora, pensa ai dati come a un elemento che si può archiviare. Ogni volta che vuoi cambiare quel valore, devi leggerlo, apportare la modifica, quindi scrivere qual è il nuovo valore. Se stiamo parlando del punteggio in una partita di calcio, ogni volta che una squadra segna devi fare una lettura (per vedere qual era l'ultimo totale), aggiungere il nuovo gol e poi riscrivere il punteggio aggiornato.

Un esempio concreto per Arclight è il registro dell'esperienza di ogni Mini. Dopo ogni Missione, viene aggiunta una riga al registro di ogni Mini, in cui viene indicato l'ammontare di esperienza ottenuto da quel Mini. Dopo cinque Missioni, si potrebbero avere delle voci simili a queste:

Tabella 1

Utente

Mini

Valore

Ora

Andy

Bruto Gnoll

3

Lunedì 14:00

Andy

Cavaliera di Grifoni

3

Lunedì 14:05

Andy

Bruto Gnoll

3

Lunedì 14:10

Andy

Pilota S.A.L.V.O.

3

Lunedì 14:15

Andy

Bruto Gnoll

3

Lunedì 14:20

Alla fine, per calcolare l'esperienza totale dei Mini, raduniamo tutte queste voci presenti nel registro, le raggruppiamo in base ai Mini e sommiamo i valori dell'esperienza. I risultati sarebbero questi:

Tabella 2

Utente

Mini

Totale

Andy

Bruto Gnoll

9

Andy

Cavaliera di Grifoni

3

Andy

Pilota S.A.L.V.O.

3


Conclusione del processo di rollup

Ora che abbiamo stabilito come archiviare l'esperienza, diamo un'occhiata a come si possono perfezionare i calcoli che devono essere eseguiti per ogni Mini. Archiviare delle righe singole per l'esperienza ottenuta da ogni Mini sarebbe un processo intenso da leggere. Ci sarebbero troppe righe e la tabella diventerebbe LUNGHISSIMA. Diciamo che tipicamente chi gioca ad Rumble fa circa 15 Incarichi o scontri PVP al giorno, 20 Burrasche alla settimana per l'Oro e una Spedizione alla settimana per potenziare l'armata. Sarebbero 100 voci in una settimana. Dopo circa 3 mesi, le voci sarebbero circa 1.260. Ora, se volessimo calcolare i totali, dovremmo recuperarli attraverso il lento sistema di lettura di Cassandra. Argh.

Per questo abbiamo programmato una soluzione: i rollup. Un rollup è un calcolo di valori fino a un certo punto nel tempo. In questo caso, li sommiamo tutti insieme e li rappresentiamo in un'unica riga. Quindi, li archiviamo in una seconda tabella. È evidente, a questo punto, quanto un timestamp sia utile. Parlando dell'esperienza, le chiavi sono il giocatore e il Mini, e il calcolo è una somma.Facciamo un rollup di tutte le voci del registro fino a martedì alle 00:00 e memorizziamo i risultati in una seconda tabella di Cassandra. Le voci sarebbero queste:

Tabella 3

Utente

Mini

Totale

Ora fine

Andy

Bruto Gnoll

9

Martedì 00:00

Andy

Cavaliera di Grifoni

3

Martedì 00:00

Andy

Pilota S.A.L.V.O.

3

Martedì 00:00

Mercoledì poi fai altre partite e generi altre voci per l'esperienza. La tabella dell'esperienza originale sarebbe cresciuta.

Tabella 4

Utente

Mini

Valore

Ora

Andy

Bruto Gnoll

3

Lunedì 14:00

Andy

Cavaliera di Grifoni

3

Lunedì 14:05

Andy

Bruto Gnoll

3

Lunedì 14:10

Andy

Pilota S.A.L.V.O.

3

Lunedì 14:15

Andy

Bruto Gnoll

3

Lunedì 14:20

Andy

Pilota S.A.L.V.O.

3

Mercoledì 00:00

Andy

Catena di Fulmini

3

Mercoledì 00:05

Andy

Cavaliera di Grifoni

3

Mercoledì 00:10

Ora, facciamo una ricerca completa e ricalcoliamo quali sono i livelli effettivi dei Mini dopo aver giocato. Creiamo una query con entrambe le tabelle - la tabella di rollup per la singola voce e il registro per le voci successive a quel momento - e riassumiamo i valori, ottenendo una tabella dell'esperienza totale finale. Quindi, leggiamo la Tabella 3 e poi facciamo una query con la Tabella 4 solo per quei dati che esistono dopo ogni riga della Tabella 3. Ecco una lista dei passi che dovremmo fare:

  1. Leggere una riga dalla Tabella 3

Cavaliera di Grifoni

3

Martedì 00:00

  1. Leggere le righe dalla Tabella 3 per la Cavaliera di Grifoni dopo martedì alle 00:00

Cavaliera di Grifoni

3

Mercoledì 00:10

  1. Ora sommiamo entrambi i set di dati per generale il totale:

Cavaliera di Grifoni

6

È evidente come questo approccio creerebbe un cortocircuito di parecchie letture dalla Tabella 4.

Si potrebbe pensare che sia necessario semplicemente archiviare i dati appena calcolati dopo aver archiviato una nuova voce del registro. E sulla carta potrebbe anche sembrare una buona soluzione, ma potrebbe portare a un'incoerenza dei dati e a un degrado della performance. Un esempio di un possibile degrado della performance è che il sistema sarebbe impegnato a ricalcolare e archiviare i dati, spesso per la necessità di leggerli e quindi scriverli. Ma noi dobbiamo bilanciare i calcoli, dovendo al contempo fornire prestazioni adeguate per il gioco a tutti i giocatori e le giocatrici.

Un esempio di incoerenza dei dati riguarderebbe i conti di fine partita. La nostra infrastruttura server e la nostra piattaforma supportano i messaggi "in ritardo". Un esempio potrebbe essere un problema temporaneo tra due centri di dati (A e B) che fa sì che il messaggio che fornisce a giocatori e giocatrici l'esperienza per il suo Mini venga inviato da A ma non venga immediatamente consegnato a B. Immagina questo scenario: stai giocando una partita subito prima di mezzanotte. Giochi e vinci. È mercoledì e sono le 23:59. Il processore di rollup è stato configurato per essere eseguito quotidianamente per tutti i dati fino al giorno corrente. Quindi, il processo dovrebbe iniziare a mezzanotte e calcolare i valori totali dell'esperienza fino a giovedì alle 00:00. Questi dati verrebbero archiviati nella seconda tabella e le nuove letture sull'esperienza cercherebbero l'ultimo valore memorizzato, riassumendo tutto dopo giovedì alle 00:00. Quello che succede con un messaggio "in ritardo" è che noi lo riceveremmo, ma noteremmo che la partita è finita mercoledì alle 23:59 e quindi scriveremmo la voce dell'esperienza con quell'ora. La tabella di rollup non includerebbe questa voce e anche la query per i dati successivi non mostrerebbe questa voce. Questi dati incompleti sarebbero un problema. E che dire dei messaggi che arrivano con giorni di ritardo? Be', abbiamo messo in atto un sistema di monitoraggio e avvisi in atto che garantisce quasi sempre che queste nuove informazioni vengano elaborate in tempo per il successivo rollup.


Calcolare i livelli dei Mini

Tornando a guardare la tabella dell'esperienza totale, noterai che non vengono calcolati i livelli, lì. C'è solo il totale della quantità di esperienza acquisita. Separatamente, c'è una tabella statica impostata dal team di progettazione che assomiglia a quella che segue. Un Mini inizia al livello 1 e quando ottiene 1 punto d'esperienza diventa di livello 2. Poi, servono 3 punti d'esperienza per raggiungere il livello 3.

Livello

Per il livello successivo

1

1

2

3

3

6

4

10

5

20

...

...

10

250

La combinazione dei due elenchi di dati ci consente di calcolare quale sia il livello effettivo di un Mini facendo un totale parziale e ottenendo questo set di dati finale:

Mini

Livello

ESP

Per il livello successivo

Bruto Gnoll

3

5

1

Cavaliera di Grifoni

3

2

4

Pilota S.A.L.V.O.

3

2

4

Catena di Fulmini

2

2

1

Il team di progettazione ha un certo margine di manovra, quando si tratta di modificare la quantità di esperienza necessaria per salire di livello. Può decidere che è troppo difficile salire di livello ai livelli bassi e diminuire quindi l'esperienza necessaria per salire al livello successivo. Quindi, senza apportare modifiche ai dati di giocatori e giocatrici salvati nel database, i Mini otterrebbero tutti un leggero aumento del livello attuale e avrebbero bisogno di accumulare meno esperienza per passare al livello successivo. Il processo funziona anche nell'altro senso. Se un giorno il team di progettazione valuta che i livelli salgono troppo rapidamente, può decidere di aumentare i relativi valori e non fornire più alcuna esperienza aggiuntiva, ripristinando i Mini ai livelli precedenti al cambiamento. Il che non significa che non potrebbe concedere un po' di esperienza aggiuntiva ai Mini per alleviare i momenti più difficili.


Approcci alternativi

Prima di utilizzare la soluzione attuale, abbiamo esaminato diversi approcci per archiviare l'esperienza ottenuta e calcolare il risultante livello dei Mini. Alcuni portavano a tempi di inattività aggiuntivi che avrebbero potuto alterare l'esperienza di giocatori e giocatrici, mentre altri erano migliori perché fornivano una buona esperienza di gioco e tenevano il passo con la progressione di giocatori e giocatrici.

"Archiviare sempre il livello del Mini, l'ESP e i punti mancanti al livello successivo"

E se avessimo utilizzato un semplice modello SQL standard e degli aggiornamenti transazionali? Gli aggiornamenti transazionali consentono di aggiornare un insieme di dati nella loro interezza, come tutti o nessuno. Se non si riesce a scrivere un dato, vengono ripristinati tutti i dati al valore originale. Per questo si usa spesso l'acronimo A.C.I.D. (atomicity, consistency, isolation, durability), che evidenzia le caratteristiche di atomicità, consistenza, isolamento e resistenza.

Questo sistema utilizzerebbe una singola riga per mantenere l'esperienza e il livello totale di ogni Mini, rendendo le letture molto leggere.

Man mano che ogni Mini ottiene esperienza, il sistema aggiorna il Mini a 2 punti d'esperienza con 8 punti mancanti e il suo livello a 3. Una modifica di questo tipo impone una lettura dei dati, un ricalcolo e quindi una scrittura nel database. Uno schema che non funziona bene con Cassandra e i suoi punti di forza. Un altro problema con questo approccio è che la modifica delle quantità di esperienza necessaria per salire di livello richiederebbe di mettere il gioco offline per poter modificare i dati per tutti i Mini e per tutti i giocatori e le giocatrici.

"Archiviarlo come percentuale"

Abbiamo anche pensato di archiviare una percentuale per il livello successivo, per esempio:

Mini

Livello

Per il livello successivo

Bruto Gnoll

3

20%

Cavaliera di Grifoni

2

20%

Pilota S.A.L.V.O.

2

20%

Quindi, dopo una partita, dovremmo fare dei rapidi calcoli. Per esempio, se il Bruto Gnoll ottiene +3 ESP, la percentuale è +3/10. Quindi, al Bruto Gnoll daremmo il 30% di un livello, e questi sarebbero i risultati:

Mini

Livello

Per il livello successivo

Bruto Gnoll

3

50%

Cavaliera di Grifoni

2

20%

Pilota S.A.L.V.O.

2

20%

Avremmo la flessibilità necessaria per cambiare la curva di livello dei Mini, senza comportare alcun cambiamento di livello per quel Mini. Questo sistema ci fornirebbe anche una visualizzazione più semplici nell'interfaccia utente del gioco, poiché dovremmo semplicemente riempire il 30% di una barra, senza bisogno di calcoli matematici aggiuntivi. Tuttavia, questo tipo di modello comporterebbe percentuali molto piccole ai livelli più alti. Se un Mini ottiene 10 ESP per partita e occorrono 100.000 ESP per salire di livello, quel valore sarebbe uno 0,01%. Le CPU (Central Processing Units) possono gestire queste fluttuazioni, ma la precisione e l'accuratezza possono produrre diversi errori, se non si tiene conto della natura dei calcoli matematici con virgola mobile.


Alla prossima...

Sono state prese in considerazione diverse opzioni per la tecnologia dei database, quali strumenti potessero essere forniti e, infine, come archiviare e calcolare i dati. Come per qualsiasi cosa ancora in fase di sviluppo, potremmo dover cambiare tutto in seguito, se dovessimo trovare qualcosa che funziona ancora meglio, ma tutto questo ci è sembrato un buon punto di partenza per questo gioco.

Grazie per aver partecipato a questo aggiornamento sulla programmazione!

~ Andy Lim

A proposito, vorrei dichiarare ufficialmente che non sono io il bandito degli occhietti adesivi. Il mio primo giorno in questo team, il bandito ha attaccato degli occhietti adesivi sui miei monitor nuovi. Mi fissano tutto il giorno... tutto... il... giorno.