Sfondo
Roblox fornisce un set di API per interfacciarsi con i depositi di dati tramite DataStoreService .Il caso d'uso più comune per queste API è per il salvataggio, il caricamento e la replicazione dei dati del giocatore **.Cioè, dati associati al progresso del Giocatore, agli acquisti e ad altre caratteristiche della sessione che persistono tra le singole sessioni di gioco.
La maggior parte delle esperienze su Roblox utilizza queste API per implementare una forma di sistema di dati del giocatore.Queste implementazioni differiscono nel loro approccio, ma generalmente cercano di risolvere lo stesso insieme di problemi.
Problemi comuni
Ecco alcuni dei problemi più comuni che i sistemi di dati del giocatore tentano di risolvere:
Nell'accesso alla memoria: DataStoreService le richieste fanno richieste web che operano in modo asincrono e sono soggette a limiti di velocità.Questo è appropriato per un carico iniziale all'inizio della Sessione, ma non per operazioni di lettura e scrittura ad alta frequenza durante il normale corso del Partita.La maggior parte dei sistemi di dati del giocatore degli sviluppatori memorizza questi dati in memoria sul ServerRoblox, limitando le richieste DataStoreService a seguenti scenari:
- Lettura iniziale all'inizio di una Sessione
- Scrittura finale alla fine della Sessione
- Scritture periodiche ad intervalli per mitigare lo scenario in cui l'ultima scrittura fallisce
- Scrivi per garantire che i dati vengono salvati durante il processamento di un Acquista
Storage efficiente: Conservare tutti i dati della sessione di un Giocatorein una singola tabella ti consente di aggiornare più valori atomici e gestire la stessa quantità di dati in meno richieste.Rimuove anche il rischio di desincronizzazione inter-valore e rende più facile ragionare sui rollback.
Alcuni sviluppatori implementano anche una serializzazione personalizzata per compressione delle grandi strutture di dati (tipicamente per salvare il contenuto generato dagli utenti in gioco).
Replicazione: Il client ha bisogno di accesso regolare ai dati di un Giocatore(ad esempio, per aggiornare l'interfaccia utente).Un approccio generico per replicare i dati del player al client ti consente di trasmettere queste informazioni senza dover creare sistemi di replicazione personalizzati per ciascuna componente dei dati.Gli sviluppatori spesso vogliono l'opzione di essere selettivi su ciò che è e non viene replicato al client.
Gestione degli errori: Quando i DataStore non possono essere accessati, la maggior parte delle soluzioni implementerà un meccanismo di riprova e un ricorso ai dati "predefiniti".È necessaria una cura speciale per garantire che i dati di fallback non sostituiscano in seguito i dati "veri", e che ciò venga comunicato al giocatore in modo appropriato.
Riprova: Quando gli store di dati non sono accessibili, la maggior parte delle soluzioni implementa un meccanismo di riprova e un fallback ai dati predefiniti.Prendi particolare cura di assicurarti che i dati di fallback non sostituiscano in seguito i dati "veri", e comunica alla giocatrice adeguatamente la situazione.
Blocco della sessione: Se i dati di un singolo Giocatorevengono caricati e in memoria su più server, possono verificarsi problemi in cui un server salva informazioni obsolete.Questo può portare alla perdita di dati e alle falle di duplicazione degli oggetti comuni.
Gestione acquisti atomica: Verifica, premi e registra gli acquisti atomici per impedire che gli oggetti vengano persi o assegnati più volte.
Codice di codice
Roblox ha un codice di riferimento per aiutarti a progettare e costruire sistemi di dati del giocatore.Il resto di questa pagina esamina i dettagli di sfondo, le implementazioni e le avvertenze generali.
Dopo aver importato il modello in Studio, dovresti vedere la seguente struttura della cartella:

Architettura
Questo diagramma di alto livello illustra i sistemi chiave nel campione e come interagiscono con il codice nel resto dell'esperienza.

Riprova
Classe: DataStoreWrapper >
Sfondo
Poiché DataStoreService fa richieste web sotto il cappuccio, le sue richieste non sono garantite di avere successo.Quando ciò accade, i metodi DataStore lanciano errori, consentendo di gestirli.
Un "gotcha" comune può verificarsi se tenti di gestire i guasti del deposito di dati come questo:
local function retrySetAsync(dataStore, key, value)
for _ = 1, MAX_ATTEMPTS do
local success, result = pcall(dataStore.SetAsync, dataStore, key, value)
if success then
break
end
task.wait(TIME_BETWEEN_ATTEMPTS)
end
end
Mentre questo è un meccanismo di riprova perfettamente valido per una funzione generica, non è adatto per le richieste DataStoreService in quanto non garantisce l'ordine in cui le richieste vengono fatte.Conservare l'ordine delle richieste è importante per le richieste DataStoreService perché interagiscono con lo stato.Considera il seguente scenario:
- La richiesta A viene fatta per impostare il valore della chiave K a 1.
- La richiesta fallisce, quindi un tentativo di ripetizione è programmato per essere eseguito in 2 secondi.
- Prima che si verifichi il tentativo di ripresa, richiedi a B di impostare il valore di K a 2, ma il tentativo di ripresa della richiesta A sovrascrive immediatamente questo valore e imposta K a 1.
Anche se UpdateAsync opera sull'ultima versione del valore della chiave, UpdateAsync le richieste devono comunque essere elaborate per evitare stati transienti non validi (ad esempio, un acquisto sottrae monete prima che venga eseguito l'aggiunta di una moneta, causando monete negative).
Il nostro sistema di dati del giocatore utilizza una nuova classe, DataStoreWrapper, che fornisce tentativi di ripresa che sono garantiti di essere elaborati in ordine per chiave.
Avvicinamento

DataStoreWrapper fornisce metodi corrispondenti ai metodi DataStore : DataStore:GetAsync() , DataStore:SetAsync() , DataStore:UpdateAsync() e DataStore:RemoveAsync() .
Questi metodi, quando chiamati:
Aggiungi la richiesta a una coda.Ogni chiave ha la propria coda, in cui le richieste vengono elaborate in ordine e in serie.Il thread richiedente si arrende fino a quando la richiesta è completata.
Questa funzionalità è basata sulla classe ThreadQueue, che è un programmatore di task basato su coroutine e limite di tariffa.Piuttosto che restituire una promessa, ThreadQueue produce il thread attuale fino a quando l'operazione non è completata e lancia un errore se fallisce.Questo è più coerente con i modelli Luau asincroni idiomatici.
Se una richiesta fallisce, riprova con un ritiro esponenziale configurabile.Questi tentativi di ripresa fanno parte del richiamo inviato al ThreadQueue, quindi sono garantiti per completarsi prima che inizi la prossima richiesta nella coda per questa chiave.
Quando una richiesta è completata, il metodo della richiesta restituisce il modello success, result
DataStoreWrapper espone anche metodi per ottenere la lunghezza della coda per una chiave data e cancellare le richieste scadute.L'ultima opzione è particolarmente utile in scenari in cui il server si sta spegnendo e non c'è tempo per elaborare qualsiasi richiesta, ma la più recente.
Avvertenze
DataStoreWrapper segue il principio che, al di fuori degli scenari estremi, ogni richiesta di archiviazione dei dati deve essere autorizzata a completarsi (con successo o altrimenti), anche se una richiesta più recente la renda superflua.Quando si verifica una nuova richiesta, le richieste scadute non vengono rimosse dalla coda, ma vengono invece consentite di completarsi prima che venga avviata la nuova richiesta.La motivazione di questo è radicata nell'applicabilità di questo modulo come utilità di archiviazione dati generica piuttosto che uno strumento specifico per i dati del giocatore, e è la seguente:
È difficile decidere su un insieme intuitivo di regole per quando una richiesta è sicura da rimuovere dalla coda. Considera la seguente coda:
Value=0, SetAsync(1), GetAsync(), SetAsync(2)
Il comportamento previsto è che GetAsync() restituirebbe 1 , ma se rimuoviamo la richiesta SetAsync() dalla coda a causa del fatto che viene resa ridondante dalla più recente, restituirebbe 0 .
La progressione logica è che quando viene aggiunta una nuova richiesta di scrittura, viene prunata solo la Richiestascaduta più recente.UpdateAsync(), di gran lunga l'operazione più comune (e l'unica utilizzata da questo sistema), può leggere e scrivere entrambi, quindi sarebbe difficile riconciliare all'interno di questo design questo senza aggiungere complessità extra.
DataStoreWrapper potrebbe richiedere che tu specifichi se una richiesta UpdateAsync() è stata autorizzata a leggere e/o scrivere, ma non avrebbe alcuna applicabilità al nostro sistema di dati del giocatore, dove questo non può essere determinato in anticipo a causa del meccanismo di blocco della sessione (trattato in dettaglio più tardi).
Una volta rimossa dalla coda, è difficile decidere su una regola intuitiva per come questo dovrebbe essere gestito.Quando viene eseguita una richiesta DataStoreWrapper , il thread attuale viene rilasciato fino a quando non viene completata.Se rimuovessimo le richieste scadute dalla coda, dovremmo decidere se restituire false, "Removed from queue" o non restituire mai e scartare il Filoattivo.Entrambi gli approcci vengono con i propri svantaggi e scaricano ulteriore complessità sul consumatore.
In definitiva, il nostro punto di vista è che l'approccio semplice (elaborare ogni Richiesta) è preferibile qui e crea un ambiente più chiaro in cui navigare quando si affrontano problemi complessi come il blocco della sessione.L'unica eccezione a questo è durante DataModel:BindToClose() , dove la pulizia della coda diventa necessaria per salvare tutti i dati degli utenti in tempo e il valore delle chiamate individuali di funzione di ritorno non è più una preoccupazione in corso.Per tener conto di ciò, esponiamo un metodo skipAllQueuesToLastEnqueued .Per maggiori contesti, vedi Dati del giocatore.
Blocco della sessione
Classe: SessionLockedDataStoreWrapper >
Sfondo
I dati del giocatore vengono memorizzati in memoria sul server e vengono letti e scritti solo nei depositi di dati sottostanti quando necessario.Puoi leggere e aggiornare i dati del player in memoria istantaneamente senza bisogno di richieste Web e evitare di superare i limiti di DataStoreService .
Perché questo modello funzioni come previsto, è imperativo che non più di un server sia in grado di caricare i dati di un Giocatorein memoria dall'DataStore allo stesso tempo.
Ad esempio, se il server A carica i dati di un Giocatore, il server B non può caricare tali dati fino a quando il server A non rilascia il suo blocco su di essi durante un salvataggio finale.Senza un meccanismo di blocco, il server B potrebbe caricare i dati del giocatore obsoleti dal deposito di dati prima che il server A abbia la possibilità di salvare la versione più recente che ha in memoria.Quindi, se il server A salva i suoi dati più recenti dopo che il server B carica i dati obsoleti, il server B sovrascriverebbe quei dati più recenti durante il suo prossimo salvataggio.
Anche se Roblox consente solo a un client di essere connesso a un server alla volta, non puoi presumere che i dati da una sessione siano sempre salvati prima che inizi la prossima sessione.Considera i seguenti scenari che possono verificarsi quando un giocatore lascia il server A:
- Il server A fa una richiesta DataStore di salvataggio dei dati, ma la richiesta fallisce e richiede diversi tentativi per completarsi con successo.Durante il periodo di riprova, il giocatore si unisce al server B.
- Il server A fa troppi UpdateAsync() chiamate alla stessa chiave e viene limitato.La richiesta di salvataggio finale viene inserita in una coda.Mentre la richiesta è in coda, il giocatore si unisce al server B.
- Sul server A, alcun codice connesso all'evento PlayerRemoving viene generato prima che i dati del Giocatorevengano salvati.Prima che questa operazione completi, il giocatore si unisce al server B.
- Le prestazioni del server A sono degradate al punto che il salvataggio finale viene ritardato fino a quando il giocatore non si unisce al server B.
Questi scenari dovrebbero essere rari, ma si Si verificano, in particolare nelle situazioni in cui un giocatore si disconnette da un server e si connette a un altro in rapida successione (ad esempio, durante il teletrasporto).Alcuni utenti malevoli potrebbero persino tentare di abusare di questo comportamento per completare azioni senza persistere.Questo può essere particolarmente rilevante nei giochi che consentono ai giocatori di scambiare e è una fonte comune di exploit di duplicazione di oggetti.
Il blocco della sessione affronta questa vulnerabilità assicurando che quando la chiave DataStore di un Giocatoreviene letta per la prima volta dal Server, il server scrive atomically una chiave di blocco nei metadati della chiave all'interno della stessa chiamata UpdateAsync().Se questo valore di blocco è presente quando qualsiasi altro server tenta di leggere o scrivere la chiave, il server non procede.
Avvicinamento

SessionLockedDataStoreWrapper è un meta- wrapper attorno alla classe DataStoreWrapper .DataStoreWrapper fornisce funzionalità di attesa e riprova, che SessionLockedDataStoreWrapper complementa con il blocco della sessione.
SessionLockedDataStoreWrapper passa ogni DataStore richiesta—indipendentemente dal fatto che sia GetAsync , SetAsync o UpdateAsync — attraverso UpdateAsync .Questo perché UpdateAsync consente a una chiave di essere sia letta che scritta atomically.È inoltre possibile abbandonare la scrittura in base al valore letto restituendo nil nella Richiamatrasformazione.
La funzione di trasformazione passata in UpdateAsync per ogni richiesta esegue le seguenti operazioni:
Verifica che la chiave sia sicura per l'Accesso, abbandonando l'operazione se non lo è. "Sicura per l'accesso" significa:
L'oggetto metadata della chiave non include un valore non riconosciuto LockId che è stato aggiornato per l'ultima volta meno di un'ora prima della scadenza del blocco.Questo si spiega nel rispetto di una blocco impostato da un altro server e nell'ignorare quel blocco se scaduto.
Se questo server ha precedentemente posizionato il proprio valore LockId nella metadata della chiave, questo valore è ancora nella metadata della chiave.Questo spiega la situazione in cui un altro server ha assunto il blocco di questo Server(per scadenza o per forza) e lo ha rilasciato in seguito.Alternativamente formulato, anche se LockId è nil , un altro server potrebbe ancora aver sostituito e rimosso un blocco nel tempo da quando hai bloccato la chiave.
UpdateAsync esegue l'operazione DataStore richiesta dal consumatore di SessionLockedDataStoreWrapper . Ad esempio, GetAsync() traduce in function(value) return value end .
A seconda dei parametri passati nella Richiesta, UpdateAsync blocca o sblocca la chiave:
Se la chiave deve essere bloccata, UpdateAsync imposta il LockId nei metadati della chiave a un GUID.Questo ID viene memorizzato in memoria sul server in modo che possa essere verificato la prossima volta che accede alla chiave.Se il server ha già un blocco su questa chiave, non apporta modifiche.Programma anche un compito per avvisarti se non accedi nuovamente alla chiave per mantenere il blocco entro il tempo di scadenza del blocco.
Se la chiave deve essere sbloccata, UpdateAsync rimuove il LockId nel metadata della chiave.
Un gestore di riprova personalizzato viene passato nel sottostante DataStoreWrapper in modo che l'operazione venga riprova se è stata interrotta al passo 1 a causa del blocco della sessione.
Un messaggio di errore personalizzato viene anche restituito al consumatore, permettendo al sistema di dati del giocatore di segnalare un errore alternativo nel caso di blocco della sessione al client.
Avvertenze
Il regime di blocco della sessione si basa su un server che rilascia sempre il suo blocco su una chiave quando è finito con essa.Questo dovrebbe sempre accadere attraverso un'istruzione per sbloccare la chiave come parte della scrittura finale in PlayerRemoving o BindToClose() .
Tuttavia, lo sblocco può fallire in alcune situazioni. Ad esempio:
- Il server si è bloccato o DataStoreService non era in funzione per tutti i tentativi di accedere alla chiave.
- A causa di un errore nella logica o di un bug simile, l'istruzione per sbloccare la chiave non è stata eseguita.
Per mantenere il blocco su una chiave, devi accedervi regolarmente finché è caricata in memoria.Questo verrebbe normalmente fatto come parte del ciclo di salvataggio automatico in esecuzione in background nella maggior parte dei sistemi di dati del giocatore, ma questo sistema espone anche un metodo refreshLockAsync se devi farlo manualmente.
Se il tempo di scadenza del blocco è stato superato senza che il blocco venga aggiornato, qualsiasi server è libero di prendere il blocco.Se un server diverso prende il blocco, i tentativi del server attuale di leggere o scrivere la chiave falliscono a meno che non stabilisca un nuovo blocco.
Elaborazione del prodotto sviluppatore
Singleton: ReceiptHandler
Sfondo
Il richiamo ProcessReceipt esegue il lavoro critico di determinare quando terminare un Acquista.ProcessReceipt è chiamato in scenari molto specifici.Per il suo insieme di garanzie, vedi MarketplaceService.ProcessReceipt .
Sebbene la definizione di "gestione" di un acquisto possa differire tra le esperienze, utilizziamo i seguenti criteri
L'acquisto non è stato precedentemente gestito.
L'acquisto si riflette nella Sessioneattuale.
Questo richiede di eseguire le seguenti operazioni prima di restituire PurchaseGranted :
- Verifica che il PurchaseId non sia già stato registrato come gestito.
- Assegna l'acquisto nei dati del Giocatorein memoria del giocatore.
- Registra il PurchaseId come gestito nei dati del Giocatorein memoria del giocatore.
- Scrivi i dati del Giocatorein memoria del giocatore al DataStore.
Il blocco della sessione semplifica questo flusso, poiché non devi più preoccuparti dei seguenti scenari:
- I dati del lettore in memoria nel server attuale potrebbero essere obsoleti, richiedendo di recuperare l'ultimo valore da DataStore prima di verificare la storia PurchaseId
- Il richiamo per lo stesso acquisto in esecuzione su un altro Server, che richiede di leggere e scrivere l'PurchaseId storia e salvare i dati aggiornati del giocatore con l'acquisto riflesso atomico per prevenire le condizioni di gara
Il blocco della sessione garantisce che, se un tentativo di scrivere nel Giocatoredi DataStore è riuscito, nessun altro server ha letto o scritto con successo nel Giocatoredi DataStore tra i dati caricati e salvati su questo Server.In breve, i dati del player in memoria su questo server sono la versione più aggiornata disponibile.Ci sono alcune avvertenze, ma non hanno alcun impatto su questo comportamento.
Avvicinamento
I commenti in ReceiptProcessor delineano l'approccio:
Verifica che i dati del Giocatoresono attualmente caricati su questo server e che sono stati caricati senza errori.
Poiché questo sistema utilizza il blocco della sessione, questo controllo verifica anche che i dati in memoria sono la versione più aggiornata.
Se i dati del Giocatorenon sono ancora stati caricati (che è ciò che si aspetta quando un giocatore si unisce a una Gioco), attendi che i dati del Giocatoresi Caricare.Il sistema ascolta anche il giocatore che lascia il gioco prima del caricamento dei dati, poiché non dovrebbe produrre indefinitamente e bloccare nuovamente questo richiamo su questo server per questo acquisto se il giocatore si ricongiunge.
Verifica che il PurchaseId non sia già registrato come elaborato nei dati del giocatore.
A causa del blocco della sessione, l'array di PurchaseIds il sistema ha in memoria è la versione più aggiornata.Se il PurchaseId viene registrato come elaborato e riflesso in un valore che è stato caricato o salvato nel DataStore , restituisci PurchaseGranted .Se viene registrato come elaborato, ma non riflesso nel DataStore , restituisci NotProcessedYet .
Aggiorna i dati del giocatore localmente su questo server per "assegnare" l'Acquista.
ReceiptProcessor adotta un approccio di richiamo generico e assegna un diverso richiamo per ogni DeveloperProductId .
Aggiorna i dati del giocatore localmente su questo server per archiviare il PurchaseId .
Invia una richiesta per salvare i dati in memoria al DataStore, restituendo PurchaseGranted se la richiesta ha successo. Altrimenti, restituisci NotProcessedYet .
Se questa richiesta di salvataggio non ha successo, una richiesta successiva di salvataggio dei dati di sessione in memoria del Giocatorepotrebbe comunque avere successo.Durante la prossima chiamata ProcessReceipt , il passo 2 gestisce questa situazione e restituisce PurchaseGranted .
Dati del giocatore
Singletons: ,
Sfondo
I moduli che forniscono un'interfaccia per il codice di gioco per leggere e scrivere i dati della sessione del giocatore in modo sincronico sono comuni nelle esperienze Roblox.Questa sezione copre PlayerData.Server e PlayerData.Client .
Avvicinamento
PlayerData.Server e PlayerData.Client gestiscono quanto seguendo:
- Caricamento dei dati del Giocatorein memoria, incluso il trattamento dei casi in cui non riesce a Caricare
- Fornire un'interfaccia per il codice del server per interrogare e modificare i dati del giocatore
- Replicare le modifiche nei dati del Giocatoreal client in modo che il codice del client possa accedervi
- Ripetere gli errori di caricamento e/o salvataggio sul client in modo che possa mostrare dialoghi di errore
- Salvataggio dei dati del Giocatoreperiodicamente, quando il giocatore se ne va e quando il server si spegne
Carica i dati del giocatore

SessionLockedDataStoreWrapper fa una richiesta getAsync al data negozio.
Se questa richiesta fallisce, i dati predefiniti vengono utilizzati e il profilo viene contrassegnato come "errato" per garantire che non venga scritto nel deposito di dati in seguito.
Un'opzione alternativa è quella di espellere il Giocatore, ma raccomandiamo di lasciare che il giocatore giochi con i dati predefiniti e la messaggistica chiara su ciò che è accaduto piuttosto che rimuoverlo dall'esperienza.
Un carico iniziale viene inviato a PlayerDataClient contenente i dati caricati e lo stato di errore (se presente).
Tutti i thread generati utilizzando waitForDataLoadAsync per il giocatore vengono ripresi.
Fornire un'interfaccia per il codice del server
- PlayerDataServer è un singleton che può essere richiesto e accessibile da qualsiasi codice server in esecuzione nello stesso ambiente.
- I dati del giocatore sono organizzati in un dizionario di chiavi e valori.Puoi manipolare questi valori sul server utilizzando i metodi setValue, getValue, updateValue e removeValue.Questi metodi operano tutti in modo sincronico senza arrendersi.
- I metodi hasLoaded e waitForDataLoadAsync sono disponibili per garantire che i dati siano stati caricati prima che tu li acceda.Consigliamo di farlo una volta durante una schermata di caricamento prima che altri sistemi vengano avviati per evitare di dover controllare gli errori di caricamento prima di ogni interazione con i dati sul client.
- Un metodo hasErrored può interrogare se il caricamento iniziale del Giocatoreha fallito, causando loro di utilizzare i dati predefiniti.Controlla questo metodo prima di consentire al giocatore di effettuare qualsiasi acquisto, poiché gli acquisti non possono essere salvati sui dati senza un Caricareriuscito.
- Un segnale A playerDataUpdated si attiva con il player , key e value ogni volta che i dati di un Giocatorevengono modificati.I sistemi individuali possono iscriversi a questo.
Replica le modifiche al client
- Qualsiasi modifica ai dati del giocatore in PlayerDataServer viene replicata a PlayerDataClient , a meno che quella chiave sia stata contrassegnata come privata usando setValueAsPrivate
- setValueAsPrivate viene utilizzato per indicare le chiavi che non devono essere inviate al client
- PlayerDataClient include un metodo per ottenere il valore di una chiave (ottenere) e un segnale che si attiva quando viene aggiornato (aggiornato).Vengono anche inclusi un metodo hasLoaded e un segnale loaded, quindi il client può attendere che i dati si carichino e si replicino prima di avviare i suoi sistemi
- PlayerDataClient è un singleton che può essere richiesto e accessibile da qualsiasi codice client che viene eseguito nello stesso ambiente
Replica gli errori al client
- Gli stati di errore riscontrati durante il salvataggio o il caricamento dei dati del giocatore vengono replicati a PlayerDataClient .
- Accedi a queste informazioni con i metodi getLoadError e getSaveError e i segnali loaded e saved.
- Ci sono due tipi di errori: DataStoreError (la richiesta DataStoreService è fallita) e SessionLocked (vedi Blocco della sessione ).
- Usa questi eventi per disabilitare i prompt di acquisto del client e implementare i dialoghi di avviso. Questa immagine mostra un esempio di dialogo:

Salva i dati del giocatore

Quando il giocatore lascia il Gioco, il sistema esegue i seguenti passaggi:
- Verifica se è sicuro scrivere i dati del Giocatorenel Negoziodati.Gli scenari in cui non sarebbe sicuro includono i dati del Giocatoreche non si caricano o sono ancora in caricamento.
- Fai una richiesta attraverso il SessionLockedDataStoreWrapper per scrivere il valore attuale dei dati in memoria nel deposito dati e rimuovi il blocco della sessione una volta completato.
- Cancella i dati del Giocatore(e altre variabili come metadati e stati di errore) dalla memoria del server.
In un ciclo periodico, il server scrive i dati di ciascun Giocatorenel deposito dati (a patto che sia sicuro salvarli).Questa ridondanza di benvenuto mitiga la perdita in caso di crash del server e è anche necessaria per mantenere il blocco della sessione.
Quando viene ricevuta una richiesta di spegnimento del server, si verifica quanto segue in una RichiamaBindToClose :
- Si richiede di salvare i dati di ciascun Giocatorenel Server, seguendo il processo normalmente attraversato quando un giocatore lascia il Server.Queste richieste vengono fatte in parallelo, poiché BindToClose le richieste di richiamo hanno solo 30 secondi per completarsi.
- Per velocizzare i salvataggi, tutte le altre richieste nella coda di ogni chiave vengono cancellate dall'archivio DataStoreWrapper (vedi Riprova ).
- Il callback non viene restituito fino a quando tutte le richieste non sono state completate.