Latar Belakang
Roblox menyediakan satu set API untuk berinteraksi dengan data store melalui DataStoreService. Kasus penggunaan yang paling umum untuk API ini adalah untuk menyimpan, memuat, dan mereplikasi data pemain. Yakni, data yang terkait dengan kemajuan pemain, pembelian, dan karakteristik sesi lain yang bertahan antara sesi permainan individual.
Sebagian besar permainan di Roblox menggunakan API ini untuk mengimplementasikan beberapa bentuk sistem data pemain. Implementasi ini berbeda dalam pendekatan mereka, tetapi secara umum berusaha untuk menyelesaikan serangkaian masalah yang sama.
Masalah Umum
Berikut adalah beberapa masalah umum yang coba dipecahkan oleh sistem data pemain:
Akses dalam Memori: Permintaan DataStoreService membuat permintaan web yang beroperasi secara asinkron dan terpengaruh oleh batas laju. Ini sesuai untuk pemuatan awal di awal sesi, tetapi tidak untuk operasi baca dan tulis yang sering selama permainan biasa. Sebagian besar sistem data pemain pengembang menyimpan data ini di memori di server Roblox, membatasi permintaan DataStoreService pada skenario berikut:
- Pembacaan awal di awal sesi
- Penulisan akhir di akhir sesi
- Penulisan berkala pada interval untuk mengurangi skenario di mana penulisan akhir gagal
- Penulisan untuk memastikan data disimpan saat memproses pembelian
Penyimpanan Efisien: Menyimpan semua data sesi pemain dalam satu tabel memungkinkan Anda memperbarui beberapa nilai secara atomik dan menangani jumlah data yang sama dalam permintaan yang lebih sedikit. Ini juga menghilangkan risiko desinkronisasi antar nilai dan memudahkan rollback.
Beberapa pengembang juga mengimplementasikan serialisasi kustom untuk mengompres struktur data besar (biasanya untuk menyimpan konten yang dihasilkan pengguna dalam permainan).
Replikasi: Klien perlu akses reguler ke data pemain (misalnya, untuk memperbarui UI). Pendekatan generik untuk mereplikasi data pemain ke klien memungkinkan Anda mengirimkan informasi ini tanpa harus membuat sistem replikasi khusus untuk setiap komponen data. Pengembang sering kali ingin opsi untuk selektif mengenai apa yang direplikasi dan yang tidak ke klien.
Penanganan Kesalahan: Ketika DataStores tidak dapat diakses, sebagian besar solusi akan menerapkan mekanisme pengulangan dan fallback ke data 'default'. Perhatian khusus diperlukan untuk memastikan bahwa data fallback tidak kemudian menimpa data 'nyata', dan bahwa ini dikomunikasikan kepada pemain dengan tepat.
Pengulangan: Ketika data store tidak dapat diakses, sebagian besar solusi menerapkan mekanisme pengulangan dan fallback ke data default. Ambil perhatian khusus untuk memastikan bahwa data fallback tidak kemudian menimpa data "nyata" dan komunikasikan situasi kepada pemain dengan tepat.
Penguncian Sesi: Jika data pemain yang sama dimuat dan ada dalam memori di beberapa server, masalah dapat terjadi di mana satu server menyimpan informasi yang sudah ketinggalan zaman. Ini dapat menyebabkan kehilangan data dan celah duplikasi item yang umum.
Penanganan Pembelian Atomik: Verifikasi, berikan, dan catat pembelian secara atomik untuk mencegah item hilang atau diberikan lebih dari sekali.
Kode Contoh
Roblox memiliki kode referensi untuk membantu Anda merancang dan membangun sistem data pemain. Sisa halaman ini membahas latar belakang, detail implementasi, dan caveat umum.
Setelah Anda mengimpor model ke Studio, Anda harus melihat struktur folder berikut:

Arsitektur
Diagram tingkat tinggi ini menggambarkan sistem kunci dalam contoh dan bagaimana mereka terhubung dengan kode di sisa permainan.

Pengulangan
Class: DataStoreWrapper
Latar Belakang
Karena DataStoreService membuat permintaan web di balik layar, permintaan tersebut tidak dijamin untuk berhasil. Ketika ini terjadi, metode DataStore menghasilkan kesalahan, memungkinkan Anda untuk menanganinya.
Sebuah "gotcha" umum dapat terjadi jika Anda mencoba menangani kegagalan data store seperti ini:
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
Meskipun ini adalah mekanisme pengulangan yang valid untuk fungsi generik, ini tidak cocok untuk permintaan DataStoreService karena tidak menjamin urutan di mana permintaan dilakukan. Mempertahankan urutan permintaan adalah penting untuk permintaan DataStoreService karena mereka berinteraksi dengan keadaan. Pertimbangkan skenario berikut:
- Permintaan A dibuat untuk mengatur nilai kunci K menjadi 1.
- Permintaan gagal, sehingga sebuah pengulangan dijadwalkan untuk dijalankan dalam 2 detik.
- Sebelum pengulangan terjadi, permintaan B mengatur nilai K menjadi 2, tetapi pengulangan permintaan A segera menimpa nilai ini dan mengatur K menjadi 1.
Meskipun UpdateAsync beroperasi pada versi terbaru dari nilai kunci, permintaan UpdateAsync masih harus diproses dalam urutan untuk menghindari keadaan transien yang tidak valid (misalnya, pembelian mengurangi koin sebelum penambahan koin diproses, menghasilkan koin negatif).
Sistem data pemain kami menggunakan kelas baru, DataStoreWrapper, yang menyediakan pengulangan yang mengizinkan yang dijamin untuk diproses dalam urutan per kunci.
Pendekatan

DataStoreWrapper menyediakan metode yang sesuai dengan metode DataStore: DataStore:GetAsync(), DataStore:SetAsync(), DataStore:UpdateAsync() dan DataStore:RemoveAsync().
Metode ini, ketika dipanggil:
Menambahkan permintaan ke antrean. Setiap kunci memiliki antrean sendiri, di mana permintaan diproses dalam urutan dan secara seri. Utas yang meminta akan yield sampai permintaan selesai.
Fungsionalitas ini didasarkan pada kelas ThreadQueue, yang merupakan penjadwal tugas berbasis coroutine dan pembatas laju. Alih-alih mengembalikan sebuah janji, ThreadQueue yield utas saat ini sampai operasi selesai dan melempar kesalahan jika gagal. Ini lebih konsisten dengan pola asynchronous Luau yang idiomatis.
Jika sebuah permintaan gagal, ia mengulangi dengan penundaan eksponensial yang dapat dikonfigurasi. Pengulangan ini merupakan bagian dari callback yang diserahkan ke ThreadQueue, sehingga mereka dijamin untuk menyelesaikan sebelum permintaan berikutnya dalam antrean untuk kunci ini dimulai.
Ketika sebuah permintaan selesai, metode permintaan mengembalikan pola success, result.
DataStoreWrapper juga mengekspos metode untuk mendapatkan panjang antrean untuk kunci tertentu dan menghapus permintaan kadaluarsa. Opsi terakhir ini sangat berguna dalam skenario saat server sedang dimatikan dan tidak ada waktu untuk memproses permintaan kecuali yang terbaru.
Caveats
DataStoreWrapper mengikuti prinsip bahwa, di luar skenario ekstrem, setiap permintaan data store harus diizinkan untuk diselesaikan (berhasil atau sebaliknya), bahkan jika permintaan yang lebih baru membuatnya tidak relevan. Ketika permintaan baru terjadi, permintaan kadaluarsa tidak dihapus dari antrean, tetapi diperbolehkan untuk diselesaikan sebelum permintaan baru dimulai. Alasan untuk ini berakar pada aplikasi modul ini sebagai utilitas data store generik alih-alih alat khusus untuk data pemain, dan adalah sebagai berikut:
Sulit untuk memutuskan seperangkat aturan intuitif untuk kapan sebuah permintaan aman untuk dihapus dari antrean. Pertimbangkan antrean berikut:
Value=0, SetAsync(1), GetAsync(), SetAsync(2)
Perilaku yang diharapkan adalah bahwa GetAsync() akan mengembalikan 1, tetapi jika kita menghapus permintaan SetAsync() dari antrean karena itu telah menjadi redundant oleh yang terbaru, itu akan mengembalikan 0.
Progresi logis adalah bahwa ketika permintaan penulisan baru ditambahkan, hanya memangkas permintaan kadaluarsa sejauh permintaan bacaan terbaru. UpdateAsync(), yang jauh lebih umum dilakukan (dan satu-satunya yang digunakan oleh sistem ini), dapat membaca dan menulis, jadi sulit untuk mendamaikan ini dalam desain ini tanpa menambahkan kompleksitas ekstra.
DataStoreWrapper dapat mengharuskan Anda untuk menentukan apakah permintaan UpdateAsync() diizinkan untuk membaca dan/atau menulis, tetapi itu tidak akan relevan untuk sistem data pemain kami, di mana ini tidak dapat ditentukan sebelumnya karena mekanisme penguncian sesi (yang akan dibahas dengan lebih detail kemudian).
Setelah dihapus dari antrean, sulit untuk memutuskan aturan intuitif tentang bagaimana ini harus ditangani. Ketika sebuah permintaan DataStoreWrapper dibuat, utas saat ini akan yield sampai selesai. Jika kita menghapus permintaan kadaluarsa dari antrean, kita harus memutuskan apakah akan mengembalikan false, "Dihapus dari antrean" atau tidak mengembalikan dan membuang utas aktif. Kedua pendekatan memiliki kekurangan masing-masing dan memindahkan kompleksitas tambahan ke konsumen.
Pada akhirnya, pandangan kami adalah bahwa pendekatan sederhana (memproses setiap permintaan) adalah yang terbaik di sini dan menciptakan lingkungan yang lebih jelas untuk dinavigasi ketika mendekati isu-isu kompleks seperti penguncian sesi. Satu-satunya pengecualian untuk ini adalah selama DataModel:BindToClose(), di mana menghapus antrean menjadi perlu untuk menyimpan data semua pengguna tepat waktu dan nilai yang dikembalikan oleh panggilan fungsi individual tidak lagi menjadi perhatian yang sedang berlangsung. Untuk konteks lebih lanjut, lihat Data Pemain.
Penguncian Sesi
Class: SessionLockedDataStoreWrapper
Latar Belakang
Data pemain disimpan dalam memori di server dan hanya dibaca dari dan ditulis ke data store yang mendasarinya ketika diperlukan. Anda dapat membaca dan memperbarui data pemain dalam memori secara instan tanpa membutuhkan permintaan web dan menghindari melebihi batas DataStoreService.
Agar model ini berfungsi seperti yang diharapkan, sangat penting bahwa tidak lebih dari satu server mampu memuat data pemain ke dalam memori dari DataStore pada saat yang sama.
Sebagai contoh, jika server A memuat data pemain, server B tidak bisa memuat data itu sampai server A melepaskan kuncinya selama penyimpanan akhir. Tanpa mekanisme penguncian, server B bisa memuat data pemain yang sudah ketinggalan zaman dari data store sebelum server A memiliki kesempatan untuk menyimpan versi terbaru yang ada dalam memori. Kemudian jika server A menyimpan data barunya setelah server B memuat data yang sudah ketinggalan zaman, server B akan menimpa data yang lebih baru itu pada penyimpanan berikutnya.
Meskipun Roblox hanya mengizinkan klien terhubung ke satu server pada satu waktu, Anda tidak dapat mengasumsikan bahwa data dari satu sesi selalu disimpan sebelum sesi berikutnya dimulai. Pertimbangkan skenario berikut yang dapat terjadi ketika seorang pemain meninggalkan server A:
- Server A membuat permintaan DataStore untuk menyimpan data mereka, tetapi permintaan gagal dan memerlukan beberapa pengulangan untuk berhasil diselesaikan. Selama periode pengulangan, pemain bergabung dengan server B.
- Server A membuat terlalu banyak panggilan UpdateAsync() pada kunci yang sama dan dibatasi. Permintaan penyimpanan akhir ditempatkan dalam antrean. Sementara permintaan berada dalam antrean, pemain bergabung dengan server B.
- Di server A, beberapa kode terhubung dengan peristiwa PlayerRemoving yield sebelum data pemain disimpan. Sebelum operasi ini selesai, pemain bergabung dengan server B.
- Kinerja server A telah memburuk sampai titik di mana penyimpanan akhir tertunda hingga setelah pemain bergabung dengan server B.
Skenario-skenario ini seharusnya jarang terjadi, tetapi memang terjadi, terutama dalam situasi di mana seorang pemain terputus dari satu server dan terhubung ke server lain dengan cepat (misalnya, saat teleportasi). Beberapa pengguna yang berniat jahat mungkin bahkan mencoba menyalahgunakan perilaku ini untuk menyelesaikan tindakan tanpa persisten. Ini bisa sangat berdampak dalam permainan yang memungkinkan pemain untuk berdagang dan merupakan sumber umum dari eksploitasi duplikasi item.
Penguncian sesi mengatasi kerentanan ini dengan memastikan bahwa ketika kunci DataStore seorang pemain pertama kali dibaca oleh server, server secara atomik menulis kunci kunci ke metadata kunci di dalam panggilan UpdateAsync() yang sama. Jika nilai kunci ini hadir ketika server lain mencoba membaca atau menulis kunci, server tidak melanjutkan.
Pendekatan

SessionLockedDataStoreWrapper adalah meta-wrapper di sekitar kelas DataStoreWrapper. DataStoreWrapper menyediakan fungsionalitas antrean dan pengulangan, yang dilengkapi oleh SessionLockedDataStoreWrapper dengan penguncian sesi.
SessionLockedDataStoreWrapper meneruskan setiap permintaan DataStore—apa pun apakah itu GetAsync, SetAsync atau UpdateAsync—melalui UpdateAsync. Ini karena UpdateAsync memungkinkan kunci untuk dibaca dan ditulis secara atomik. Juga memungkinkan untuk membatalkan penulisan berdasarkan nilai yang dibaca dengan mengembalikan nil dalam callback transformasi.
Fungsi transformasi yang diserahkan ke UpdateAsync untuk setiap permintaan melakukan operasi berikut:
Memverifikasi bahwa kunci aman untuk diakses, membatalkan operasi jika tidak. "Aman untuk diakses" berarti:
- Objek metadata kunci tidak menyertakan nilai LockId yang tidak dikenali yang terakhir diperbarui kurang dari waktu kedaluwarsa kunci.
- Jika server ini telah menempatkan nilai LockId nya sendiri dalam metadata kunci sebelumnya, maka nilai ini masih ada di metadata kunci.
UpdateAsync melakukan operasi DataStore yang diminta oleh konsumen SessionLockedDataStoreWrapper. Sebagai contoh, GetAsync() diterjemahkan menjadi function(value) return value end.
Bergantung pada parameter yang diserahkan ke permintaan, UpdateAsync mengunci atau membuka kunci kunci:
Jika kunci akan dikunci, UpdateAsync akan menetapkan LockId dalam metadata kunci ke sebuah GUID. GUID ini disimpan dalam memori di server sehingga dapat diverifikasi saat akses kunci kembali. Jika server sudah memiliki kunci pada kunci ini, tidak ada perubahan yang dilakukan. Ini juga menjadwalkan tugas untuk memperingatkan jika Anda tidak mengakses kunci lagi untuk mempertahankan kunci dalam waktu kedaluwarsa kunci.
Jika kunci akan dibuka kuncinya, UpdateAsync menghapus LockId dalam metadata kunci.
Handler pengulangan kustom diserahkan ke DataStoreWrapper yang mendasarinya agar operasi diulang jika dibatalkan pada langkah 1 karena sesi sedang dikunci.
Pesan kesalahan kustom juga dikembalikan kepada konsumen, memungkinkan sistem data pemain untuk melaporkan kesalahan alternatif dalam kasus penguncian sesi kepada klien.
Caveats
Regime penguncian sesi bergantung pada server selalu melepaskan kunci pada kunci ketika sudah selesai menggunakannya. Ini harus selalu terjadi melalui instruksi untuk membuka kunci kunci sebagai bagian dari penyimpanan akhir dalam PlayerRemoving atau BindToClose().
Namun, membuka kunci dapat gagal dalam situasi tertentu. Sebagai contoh:
- Server tidak merespon atau DataStoreService tidak dapat dioperasikan untuk semua upaya untuk mengakses kunci.
- Karena kesalahan dalam logika atau bug serupa, instruksi untuk membuka kunci kunci tidak dibuat.
Untuk mempertahankan kunci pada kunci, Anda harus secara teratur mengaksesnya selama kunci tersebut dimuat dalam memori. Ini biasanya dilakukan sebagai bagian dari loop penyimpanan otomatis yang berjalan di latar belakang di sebagian besar sistem data pemain, tetapi sistem ini juga mengekspos metode refreshLockAsync jika Anda perlu melakukannya secara manual.
Jika waktu kedaluwarsa kunci telah terlampaui tanpa kunci diperbarui, maka server mana pun bebas untuk mengambil alih kunci. Jika server lain mengambil kunci, upaya oleh server saat ini untuk membaca atau menulis kunci akan gagal kecuali jika ia menetapkan kunci baru.
Pengolahan Produk Pengembang
Singleton: ReceiptHandler
Latar Belakang
Callback ProcessReceipt melakukan pekerjaan penting untuk menentukan kapan menghentikan pembelian. ProcessReceipt dipanggil dalam skenario yang sangat spesifik. Untuk jaminan setnya, lihat MarketplaceService.ProcessReceipt.
Meskipun definisi "menangani" pembelian dapat berbeda antara permainan, kami menggunakan kriteria berikut:
Pembelian belum ditangani sebelumnya.
Pembelian tercermin dalam sesi saat ini.
Ini memerlukan pelaksanaan operasi berikut sebelum mengembalikan PurchaseGranted:
- Verifikasi bahwa PurchaseId belum tercatat sebagai ditangani.
- Berikan pembelian dalam data pemain dalam memori pemain.
- Catat PurchaseId sebagai yang ditangani dalam data pemain dalam memori pemain.
- Tulis data pemain dalam memori pemain ke DataStore.
Penguncian sesi menyederhanakan alur ini, karena Anda tidak perlu lagi khawatir tentang skenario berikut:
- Data pemain dalam memori di server saat ini berpotensi sudah ketinggalan zaman, memerlukan Anda untuk mengambil nilai terbaru dari DataStore sebelum memverifikasi riwayat PurchaseId.
- Callback untuk pembelian yang sama berjalan di server lain, memerlukan Anda untuk membaca dan menulis riwayat PurchaseId dan menyimpan data pemain yang diperbarui dengan pembelian yang tercermin secara atomik untuk mencegah kondisi balapan.
Penguncian sesi menjamin bahwa, jika upaya untuk menulis ke DataStore pemain berhasil, tidak ada server lain yang berhasil membaca atau menulis ke DataStore pemain antara data yang dimuat dan disimpan di server ini. Singkatnya, data pemain dalam memori di server ini adalah versi terbaru yang tersedia. Ada beberapa caveat, tetapi mereka tidak mempengaruhi perilaku ini.
Pendekatan
Komentar dalam ReceiptProcessor menggambarkan pendekatannya:
Verifikasi bahwa data pemain saat ini dimuat di server ini dan dimuat tanpa kesalahan.
Karena sistem ini menggunakan penguncian sesi, pemeriksaan ini juga memverifikasi bahwa data dalam memori adalah versi terbaru.
Jika data pemain belum dimuat (yang diharapkan ketika seorang pemain bergabung ke permainan), tunggu hingga data pemain dimuat. Sistem juga mendengarkan pemain yang meninggalkan permainan sebelum data mereka dimuat, karena tidak boleh yield selamanya dan memblokir callback ini untuk dipanggil lagi di server ini untuk pembelian ini jika pemain bergabung kembali.
Verifikasi bahwa PurchaseId tidak sudah tercatat sebagai diproses dalam data pemain.
Karena penguncian sesi, array PurchaseIds yang dimiliki sistem dalam memori adalah versi terbaru. Jika PurchaseId tercatat sebagai diproses dan tercermin dalam nilai yang telah dimuat ke atau disimpan ke DataStore, kembalikan PurchaseGranted. Jika tercatat sebagai diproses, tetapi tidak tercermin dalam DataStore, kembalikan NotProcessedYet.
Perbarui Data Pemain secara lokal di server ini untuk "memberi" pembelian.
ReceiptProcessor mengambil pendekatan callback generik dan menetapkan callback yang berbeda untuk setiap DeveloperProductId.
Perbarui data pemain secara lokal di server ini untuk menyimpan PurchaseId.
Kirim permintaan untuk menyimpan data dalam memori ke DataStore, mengembalikan PurchaseGranted jika permintaan berhasil. Jika tidak, kembalikan NotProcessedYet.
Jika permintaan penyimpanan ini tidak berhasil, permintaan berikutnya untuk menyimpan data sesi pemain dalam memori masih bisa berhasil. Selama panggilan ProcessReceipt berikutnya, langkah 2 menangani situasi ini dan mengembalikan PurchaseGranted.
Data Pemain
Singletons: PlayerData.Server, PlayerData.Client
Latar Belakang
Modul yang menyediakan antarmuka untuk kode untuk membaca dan menulis data sesi pemain secara sinkron adalah umum dalam permainan Roblox. Bagian ini mencakup PlayerData.Server dan PlayerData.Client.
Pendekatan
PlayerData.Server dan PlayerData.Client menangani hal berikut:
- Memuat data pemain ke dalam memori, termasuk menangani kasus di mana gagal memuat
- Menyediakan antarmuka untuk kode server untuk mencari dan mengubah data pemain
- Mereplikasi perubahan dalam data pemain ke klien sehingga kode klien dapat mengaksesnya
- Mereplikasi kesalahan pemuatan dan/atau penyimpanan ke klien sehingga dapat menampilkan dialog kesalahan
- Menyimpan data pemain secara berkala, saat pemain meninggalkan, dan saat server dimatikan
Memuat Data Pemain

SessionLockedDataStoreWrapper membuat permintaan getAsync ke data store.
Jika permintaan ini gagal, data default digunakan dan profil ditandai sebagai "errored" untuk memastikan tidak ditulis ke data store nanti.
Opsi alternatif adalah untuk mengusir pemain, tetapi kami merekomendasikan untuk membiarkan pemain bermain dengan data default dan penyampaian pesan yang jelas tentang apa yang terjadi daripada mengeluarkan mereka dari permainan.
Payload awal dikirim ke PlayerDataClient yang berisi data yang dimuat dan status kesalahan (jika ada).
Semua utas yang yield menggunakan waitForDataLoadAsync untuk pemain akan dilanjutkan.
Menyediakan Antarmuka untuk Kode Server
- PlayerDataServer adalah singleton yang dapat dipanggil dan diakses oleh kode server mana pun yang berjalan di lingkungan yang sama.
- Data pemain diorganisasikan menjadi kamus kunci dan nilai. Anda dapat memanipulasi nilai ini di server menggunakan metode setValue, getValue, updateValue dan removeValue. Metode ini semua beroperasi secara sinkron tanpa yield.
- Metode hasLoaded dan waitForDataLoadAsync tersedia untuk memastikan data telah dimuat sebelum Anda mengaksesnya. Kami merekomendasikan melakukan ini sekali selama layar pemuatan sebelum sistem lain dimulai untuk menghindari memeriksa kesalahan pemuatan sebelum setiap interaksi dengan data di klien.
- Metode hasErrored dapat menanyakan jika pemuatan awal pemain gagal, menyebabkan mereka menggunakan data default. Periksa metode ini sebelum memungkinkan pemain melakukan pembelian, karena pembelian tidak dapat disimpan ke data tanpa pemuatan yang berhasil.
- Sinya playerDataUpdated terpicu dengan player, key, dan value setiap kali data pemain diubah. Sistem individual dapat berlangganan pada ini.
Mereplikasi Perubahan ke Klien
- Setiap perubahan pada data pemain dalam PlayerDataServer direplikasi ke PlayerDataClient, kecuali kunci tersebut ditandai sebagai privat menggunakan setValueAsPrivate.
- setValueAsPrivate digunakan untuk menunjukkan kunci yang tidak boleh dikirim ke klien.
- PlayerDataClient mencakup metode untuk mendapatkan nilai kunci (get) dan sinyal yang terpicu saat diperbarui (updated). Metode hasLoaded dan sinyal loaded juga disertakan, sehingga klien dapat menunggu pemuatan data & replikasi sebelum memulai sistemnya.
- PlayerDataClient adalah singleton yang dapat dipanggil dan diakses oleh kode klien mana pun yang berjalan di lingkungan yang sama.
Mereplikasi Kesalahan ke Klien
- Status kesalahan yang dialami saat menyimpan atau memuat data pemain direplikasi ke PlayerDataClient.
- Akses informasi ini dengan metode getLoadError dan getSaveError, bersama dengan sinyal loaded dan saved.
- Ada dua jenis kesalahan: DataStoreError (permintaan DataStoreService gagal) dan SessionLocked (lihat Penguncian Sesi).
- Gunakan kejadian ini untuk menonaktifkan prompt pembelian di klien dan menerapkan dialog peringatan. Gambar ini menunjukkan contoh dialog:

Menyimpan Data Pemain

Ketika pemain meninggalkan permainan, sistem mengambil langkah-langkah berikut:
- Periksa apakah aman untuk menulis data pemain ke data store. Skenario di mana tidak aman termasuk data pemain gagal dimuat atau masih dalam proses pemuatan.
- Buat permintaan melalui SessionLockedDataStoreWrapper untuk menulis nilai data dalam memori saat ini ke data store dan menghapus penguncian sesi setelah selesai.
- Hapus data pemain (dan variabel lain seperti metadata dan status kesalahan) dari memori server.
Dalam loop berkala, server menulis data setiap pemain ke data store (selama aman untuk menyimpan). Redundansi sambutan ini mengurangi kehilangan jika server crash dan juga diperlukan untuk memelihara penguncian sesi.
Ketika permintaan untuk mematikan server diterima, yang terjadi dalam callback BindToClose:
- Permintaan dibuat untuk menyimpan data setiap pemain di server, mengikuti proses yang biasanya dilalui ketika seorang pemain meninggalkan server. Permintaan ini dibuat secara paralel, karena callback BindToClose hanya memiliki 30 detik untuk diselesaikan.
- Untuk mempercepat penyimpanan, semua permintaan lain dalam antrean setiap kunci dihapus dari DataStoreWrapper yang mendasarinya (lihat Pengulangan).
- Callback tidak akan kembali sampai semua permintaan selesai.