Arka Plan
Roblox, DataStoreService üzerinden veri depolarıyla etkileşim kurmak için bir dizi API sağlar. Bu API'lerin en yaygın kullanım durumu, oyuncu verilerini kaydetmek, yüklemek ve çoğaltmaktır. Yani, bir oyuncunun ilerleyişine, satın almalarına ve bireysel oyun oturumları arasında devam eden diğer oturum özelliklerine bağlı veriler.
Roblox'taki çoğu oyun, bir tür oyuncu veri sistemi uygulamak için bu API'leri kullanır. Bu uygulamalar yaklaşımlarında farklılık göstermekle birlikte, genel olarak aynı sorunları çözmeyi amaçlamaktadır.
Yaygın sorunlar
Aşağıda oyuncu veri sistemlerinin çözmeye çalıştığı en yaygın sorunlar bulunmaktadır:
Bellek Erişimi: DataStoreService istekleri web istekleri yapar ki bunlar asenkron olarak çalışır ve hız limilerine tabidir. Bu, oturum başlangıcındaki ilk yükleme için uygundur, ancak normal oyun sürecinde yüksek frekanslı okuma ve yazma işlemleri için uygun değildir. Çoğu geliştiricinin oyuncu veri sistemleri, bu verileri Roblox sunucusunda bellekte depolar, DataStoreService isteklerini aşağıdaki senaryolarla sınırlı tutar:
- Bir oturumun başlangıcında ilk okuma
- Oturumun sonunda son yazma
- Son yazmanın başarısız olduğu senaryoya karşı yatay yazmalar
- Satın alma işlemi işlenirken verilerin kaydedildiğinden emin olmak için yazmalar
Verimli Depolama: Bir oyuncunun oturum verilerini tek bir tabloda depolamak, birden fazla değeri atomik olarak güncelleyebilmenizi ve daha az istekle aynı veriyi işleyebilmenizi sağlar. Ayrıca, değerler arası senkronizasyon riskini ortadan kaldırır ve geri alma işlemlerini daha kolay hale getirir.
Bazı geliştiriciler, büyük veri yapılarını sıkıştırmak için özel serileştirme uygulamaları da geliştirmektedirler (genellikle oyundaki kullanıcı tarafından oluşturulan içerikleri kaydetmek için).
Çoğaltma: İstemci, bir oyuncunun verilerine düzenli erişime ihtiyaç duyar (örneğin, kullanıcı arayüzünü güncellemek için). Oyuncu verilerini istemciye çoğaltmanın genel bir yaklaşımı, bu verileri her veri bileşeni için özel çoğaltma sistemleri oluşturmadan iletebilmenizi sağlar. Geliştiriciler genellikle istemciye nelerin ve nelerin çoğaltılmadığını seçme seçeneği isterler.
Hata Yönetimi: Veri Depolarına erişilemiyorsa, birçok çözüm bir yeniden deneme mekanizması ve 'varsayılan' verilere geri dönüş uygular. Varsayılan verilerin sonradan 'gerçek' verileri geçmemesini sağlamak ve bunu oyuncuya uygun bir şekilde iletmek için özel dikkat gereklidir.
Yeniden Denemeler: Veri depolarına erişilemezse, çoğu çözüm bir yeniden deneme mekanizması ve varsayılan verilere geri dönüş uygular. Varsayılan verilerin sonradan "gerçek" verileri geçmemesini sağlamak ve durumu oyuncuya uygun bir şekilde iletmek için özel dikkat gösterin.
Oturum Kilitleme: Eğer bir oyuncunun verileri birden fazla sunucuda yüklenirse ve bellekte tutulursa, bir sunucunun güncel olmayan bilgileri kaydetmesiyle ilgili sorunlar ortaya çıkabilir. Bu durum veri kaybına ve yaygın eşya çoğaltma açıklarına yol açabilir.
Atomik Satın Alma Yönetimi: Eşyaların kaybolmasını veya birden fazla kez verilmesini engellemek için satın alma işlemlerini atomik olarak doğrulamak, ödüllendirmek ve kaydetmek.
Örnek kod
Roblox, oyuncu veri sistemleri tasarlamanıza ve geliştirmenize yardımcı olmak için referans kodu sağlamaktadır. Bu sayfanın geri kalanı arka plan, uygulama detayları ve genel uyarılar üzerinde durmaktadır.
Modeli Studio'ya aktardıktan sonra aşağıdaki klasör yapısını görmelisiniz:

Mimari
Bu yüksek seviyeli diyagram, örnek içindeki ana sistemleri ve bunların oyunun geri kalan kısmındaki kodla nasıl etkileşimde bulunduğunu göstermektedir.

Yeniden Denemeler
Sınıf: DataStoreWrapper
Arka Plan
DataStoreService arka planda web istekleri yaptığından, bu isteklerin başarı garantisi yoktur. Bu durumda, DataStore yöntemleri hata fırlatır, böylece bu hataları yönetebilirsiniz.
Genel bir "tuzağa düşürme", veri deposu hatalarını şu şekilde yönetmeye çalıştığınızda ortaya çıkabilir:
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
Bu, genel bir fonksiyon için mükemmel geçerli bir yeniden deneme mekanizması olmasına rağmen, DataStoreService istekleri için uygun değildir çünkü isteklerin ne sıklıkla yapılacağına dair bir garanti vermez. İsteklerin sırasını korumak, DataStoreService istekleri için önemlidir çünkü bunlar durumla etkileşimde bulunur. Aşağıdaki senaryoyu düşünün:
- A anahtarı K değerini 1 olarak ayarlamak için bir istek yapılır.
- İstek başarısız olur, bu yüzden 2 saniye içinde çalışacak bir yeniden deneme planlanır.
- Yeniden denemeden önce, B isteği K değerini 2 olarak ayarlar, fakat A isteğinin yeniden denemesi hemen bu değeri yazar ve K'yı 1 yapar.
UpdateAsync yalnızca anahtarın değerinin en son sürümü üzerinde işlem yapsa da, UpdateAsync istekleri geçersiz geçici durumları önlemek için hâlâ sırayla işlenmelidir (örneğin, bir satın alma işlemi, sonraki işlem gerçekleşmeden önce paraları azaltır ve bu, negatif paraya yol açar).
Bizim oyuncu veri sistemimiz, DataStoreWrapper adında yeni bir sınıf kullanır; bu sınıf, anahtar başına sıralı işlenmesi garanti edilen yeniden denemeler sağlar.
Yaklaşım

DataStoreWrapper, DataStore yöntemleri olan DataStore:GetAsync(), DataStore:SetAsync(), DataStore:UpdateAsync() ve DataStore:RemoveAsync() ile eşleşen yöntemler sağlar.
Bu yöntemler çağrıldığında:
İsteği bir kuyruğa ekler. Her bir anahtarın kendine ait kuyruğu vardır ve istekler sırayla ve seri olarak işlenir. İsteği yapan thred, istek tamamlanana kadar bekler.
Bu işlevsellik, bir coroutine-destekli görev zamanlayıcısı ve hız sınırlayıcı olan ThreadQueue sınıfına dayanmaktadır. Bir vaad döndürmek yerine, ThreadQueue mevcut thredi, işlem tamamlanana kadar bekletir ve hata olursa hata fırlatır. Bu, idiyomatik asenkron Luau desenleriyle daha tutarlıdır.
Eğer bir istek başarısız olursa, yapılandırılabilir bir üssel geri dönüş ile yeniden dener. Bu yeniden denemeler, ThreadQueue'ye gönderilen geri çağırma işlemini içerir, bu yüzden kuyruktaki bu anahtar için bir sonraki isteğin başlamasından önce tamamlanması garanti edilir.
Bir istek tamamlandığında, istek yöntemi success, result desenine sahip döner.
DataStoreWrapper, belirli bir anahtar için kuyruk uzunluğunu almanızı ve eski istekleri temizlemenizi sağlayan yöntemler de sunar. İkinci seçenek, sunucunun kapandığı senaryolar için özellikle yararlıdır ve bu durumda yalnızca en son isteklerin işlenmesi için zaman yoktur.
Uyarılar
DataStoreWrapper, aşırı senaryolar dışında, her veri deposu isteğinin başarılı ya da başarısız tamamlanmasına izin verilmesi gerektiği ilkesine uyar. Yeni bir istek gerçekleştiğinde, eski istekler kuyruktan çıkarılmaz, bunun yerine yeni isteğin başlanmasından önce tamamlanmasına izin verilir. Bunun nedenleri, bu modülün, bir oyuncu verileri aracı olmaktan ziyade genel bir veri deposu aracı olarak uygulanabilirliği ile ilgilidir ve şunlardır:
Bir isteğin ne zaman kuyruktan çıkarılması gerektiği hakkında sezgisel bir kural seti belirlemek zordur. Aşağıdaki kuyrukları düşünün:
Value=0, SetAsync(1), GetAsync(), SetAsync(2)
Beklenen davranış, GetAsync()'nin 1 döndürmesidir. Fakat en son yapılan isteğin bu isteği gereksiz kıldığı düşüncesiyle SetAsync() isteğini kuyruktan çıkarırsak, bu 0 döndürür.
Mantıksal ilerleme, yeni bir yazma isteği eklendiğinde yalnızca en son okuma isteğine kadar eski isteklerin budanması gerektiğidir. UpdateAsync(), şüphesiz en yaygın olan işlem (ve bu sistem tarafından kullanılan tek işlem) hem okuma hem de yazma yapabildiği için bu tasarım içerisinde bu durumu uzatmak zordur.
DataStoreWrapper, UpdateAsync() isteğinin okuma ve/veya yazma izni verip vermediğini belirtmenizi gerektirebilir, fakat bu, oturum kilitleme mekanizması nedeniyle önceden belirlenemediği için oyuncu veri sistemimizle uygulamaya uygulanamaz (daha sonra daha ayrıntılı ele alınacaktır).
Kuyruktan çıkarıldıktan sonra, bunun nasıl işleneceği konusunda sezgisel bir kural koymak zordur. Bir DataStoreWrapper isteği yapıldığında, mevcut thread, tamamlanana kadar bekletilir. Eğer eski istekleri kuyruktan çıkartırsak, hangi yöntemi kullanmalıyız: false, "Kuyruktan çıkarıldı" döndürmek mi yoksa asıl thread'i bırakıp hiçbir şey döndürmemek mi? Her iki yaklaşımın da kendine özgü sakıncaları vardır ve tüketiciye fazladan karmaşıklık yükler.
Sonuç olarak, basit yaklaşımın (her isteği işlemek) burada tercih edilebilir olduğu ve oturum kilitleme gibi karmaşık konularla başa çıkmada daha açık bir ortam yarattığını düşünüyoruz. Tek istisna DataModel:BindToClose() sırasında, burada kuyrukların temizlenmesi, tüm kullanıcı verilerini zamanında kaydedebilmek için gerekli hale gelir ve bireysel işlev çağrılarının döndürdüğü değerler artık devam eden bir endişe konusu değildir. Bu durumu dikkate almak için, skipAllQueuesToLastEnqueued adında bir yöntem sunuyoruz. Daha fazla ayrıntı için Oyuncu Verileri bağlantısına bakın.
Oturum kilitleme
Sınıf: SessionLockedDataStoreWrapper
Arka Plan
Oyun verileri, sunucuda bellekte depolanır ve yalnızca gerektiğinde temel veri depolarından okunur ve yazılır. İnternete ihtiyaç duymadan bellek üzerindeki oyuncu verilerini anında okuyabilir ve güncelleyebilirsiniz; böylece DataStoreService limitlerini aşmaktan kaçınabilirsiniz.
Bu modelin amaçladığı gibi çalışabilmesi için, bir sunucunun aynı anda bir oyuncunun verilerini DataStore ile belleğe yükleyebildiğinden başka hiçbir sunucunun aynı anda yapmaması kritik öneme sahiptir.
Örneğin, eğer sunucu A bir oyuncunun verilerini yüklerse, sunucu B o veri yüklenene kadar bu veriyi yükleyemez. Kilitleme mekanizması olmadan, sunucu B daha güncel bir versiyonunun kaydedilmesi için sunucu A'nın şansı olmadan veri deposundan eski oyuncu verilerini yükleyebilir. Ardından, eğer sunucu A, sunucu B'nin yüklediği eski veriden sonra daha yeni verilerini kaydederse, sunucu B bu daha yeni veriyi bir sonraki kaydetme işlemi sırasında geçersiz kılar.
Roblox yalnızca bir istemcinin aynı anda bir sunucuya bağlı olmasına izin verse de, bir oturumdan verilerin her zaman kaydedildiğini varsayamazsınız. Oyuncunun sunucu A'dan ayrıldığı sırada ortaya çıkabilecek senaryoları düşünün:
- Sunucu A, oyuncunun verilerini kaydetmek için bir DataStore isteği yapar, ancak bu istek başarısız olur ve başarılı bir şekilde tamamlanması için birkaç tekrar gerektirir. Yeniden deneme dönemi boyunca oyuncu sunucu B'ye katılır.
- Sunucu A, aynı anahtar için çok fazla UpdateAsync() çağrısı yaptığında baskı altında kalır. Nihai kayıt isteği bir kuyruğa yerleştirilir. Bu istek kuyrukta iken oyuncu sunucu B'ye katılır.
- Sunucu A'da, PlayerRemoving olayına bağlı bazı kodlar oyuncunun verileri kaydedilmeden önce bekletilir. Bu işlem tamamlanmadan, oyuncu sunucu B'ye katılır.
- Sunucu A'nın performansı, nihai kaydı oyuncu sunucu B'ye katılmadan önce geciktirecek kadar kötüleşmiştir.
Bu senaryolar nadir olmalıdır, ancak özellikle bir oyuncunun bir sunucudan hızlıca çıkıp diğerine bağlandığı (örneğin, teleportasyon sırasında) durumlarda meydana gelebilir. Bazı kötü niyetli kullanıcılar bile bu davranışı kullanarak kalıcı olmayan işlemleri tamamlamak için suiistimal etme girişiminde bulunabilirler. Bu, oyuncuların ticaret yapmasına izin veren oyunlarda özellikle etkileyici olabilmekte ve eşya çoğaltma açıklarının yaygın bir kaynağını oluşturabilmektedir.
Oturum kilitleme, bir oyuncunun DataStore anahtarının sunucu tarafından ilk kez okunduğunda, sunucunun aynı UpdateAsync() çağrısı içinde anahtarın meta verilerine atomik olarak bir kilit yazmasını sağlayarak bu açığı giderir. Eğer bu kilit değeri, başka bir sunucu anahtarı okumaya veya yazmaya çalıştığında mevcutsa, sunucu işlemi sürdürmez.
Yaklaşım

SessionLockedDataStoreWrapper, DataStoreWrapper sınıfının bir meta-sarmalayıcısıdır. DataStoreWrapper kuyruklama ve yeniden deneme işlevselliği sağlar, SessionLockedDataStoreWrapper ise bunu oturum kilitleme ile tamamlar.
SessionLockedDataStoreWrapper, her DataStore isteğini — hangi GetAsync, SetAsync veya UpdateAsync olduğunu ayırmadan — UpdateAsync üzerinden geçirir. Bunun nedeni, UpdateAsync anahtarın atomik olarak hem okunup hem yazılmasına olanak sağlamasıdır. İlgili anahtar okuma temelinde yazmayı terk etme imkanı da bulunmaktadır; dönüş benzeri gerçekleşirse geri dönüş fonksiyonu içerisinde nil dönebilirsiniz.
Her isteğe yönelik olarak UpdateAsync içerisine geçirilen dönüşüm fonksiyonu, aşağıdaki işlemleri gerçekleştirmektedir:
Anahtarın güvenli bir şekilde erişilebilir olduğunu doğrular, eğer değilse işlemi terk eder. "Güvenli erişilebilir" olmak, şu anlama gelir:
Anahtarın meta veri nesnesi, kilit süresi dolmadan önce en son güncellenmiş tanınmayan bir LockId değeri içermez. Bu, başka bir server tarafından konulan kilidi göz önünde bulundurmak ve süresi dolmuşsa o kilidi dikkate almamak anlamına gelir.
Eğer bu sunucu, daha önce anahtarın meta verilerine kendi LockId değerini yazdıysa, bu değer hala anahtarın meta verilerinde bulunur. Bu, başka bir sunucunun kilidi devralmış olması (süresi dolmuşsa veya zorla) ve sonrasında serbest bırakması durumunu göz önünde bulundurur. Alternatif bir ifadeyle, LockId nil olsa bile, farklı bir sunucu, anahtarın kilidini kaldırana kadar kilidi değiştirmiş veya kaldırmış olabilir.
UpdateAsync, SessionLockedDataStoreWrapper tüketicisinin talep ettiği DataStore işlemini gerçekleştirir. Örneğin, GetAsync() bir dönüşüm fonksiyonu olarak function(value) return value end şeklinde dönüşür.
Talep edilen isteğe bağlı parametreye bağlı olarak UpdateAsync anahtarı kilitler veya kilidini açar:
Anahtar kilitlenecekse, UpdateAsync anahtarın meta verisine bir GUID ile LockId ayarlar. Bu GUID, sunucuda bellekte depolanır, böylece bir sonraki araç eriştiğinde doğrulanabilir. Eğer sunucu zaten bu anahtar üzerinde bir kilidi varsa, değişiklik yapmaz. Ayrıca, bu kilidin süre aşımına kadar korunduğundan emin olmak için yine erişim yapılmadığında sizi uyaracak bir görev planlar.
Anahtar açılacaksa, UpdateAsync anahtarın meta verisinde LockId'yı kaldırır.
Kilitin kilitli kalması durumunda işlemin bir noktada engellenmesi halinde, altın DataStoreWrapper içerisine özel bir yeniden deneme işleyici aktarılır, böylece işlem tekrar gerçekleşir.
Tüketiciye ayrıca özel bir hata mesajı da döndürülür, böylece oyuncu veri sistemi oturum kilitlenmesi durumunda istemciye çeşitli bir hata bildirebilir.
Uyarılar
Oturum kilitleme rejimi, bir sunucunun bir anahtar üzerinde kilidini bırakırken her zaman düzgün bir şekilde işlemesi gerektiğine dayanmaktadır. Bu, her zaman son yazımdaki kilidi açmak için bir talimatla yapılmalıdır; bu da PlayerRemoving veya BindToClose() işlevi ile yapılmalıdır.
Ancak, kilidini açmak bazı durumlarda başarısız olabilir. Örneğin:
- Sunucu çökmüş veya DataStoreService kilidi erişmek için yapılan tüm denemelerde çalışmaz oldu.
- Mantık hatası veya benzer bir hata nedeniyle anahtarın kilidini açma talimatı verilmemiştir.
Bir anahtar üzerindeki kilidin tutulabilmesi için, bellekte yüklü olduğu sürece sık sık erişim yapılması gerekmektedir. Bu, genelde, oyuncu veri sistemlerinin arka planda çalışan otomatik kaydetme döngüsü aracılığıyla gerçekleştirilirken, bu sistem aynı zamanda manual olarak gerçekleştirmek istiyorsanız refreshLockAsync yöntemini de sunmaktadır.
Eğer kilit süresi dolduysa ve kilit güncellenmemişse, artık herhangi bir sunucu kilidi devralabilir. Eğer farklı bir sunucu kilidi devralırsa, mevcut sunucunun anahtarı okuma veya yazma denemeleri başarısız olur, yeni bir kilit kurmadıkça.
Geliştirici Ürün İşleme
Tekil: ReceiptHandler
Arka Plan
ProcessReceipt geri çağırma işlemi, satın almanın tamamlanmasına ne zaman karar verileceğini belirleme işini kritik bir şekilde gerçekleştirir. ProcessReceipt, çok belirli durumlarda çağrılır. Garantilerin seti için MarketplaceService.ProcessReceipt'e bakın.
"Bir satın almayı işleme alma" tanımı oyunlara göre farklılık gösterebilir, ancak şu kriterleri kullanıyoruz:
Satın alma daha önce işlenmemiştir.
Satın alma, mevcut oturumda yansıtılmıştır.
Bu, PurchaseGranted döndürmeden önce aşağıdaki işlemlerin gerçekleştirilmesi gerektirir:
- PurchaseId'nın zaten işlenmiş olduğunu doğrulayın.
- Satın almayı oyuncunun bellek üzerindeki verilerine ödüllendirin.
- PurchaseId'yı oyuncunun bellek üzerindeki veri setlerinde işlenmiş olarak kayıt edin.
- Oyuncunun bellek üzerindeki veri setlerini DataStore'ya yazın.
Oturum kilitlemesi, bu akışı basitleştirir, çünkü artık aşağıdaki senaryolar hakkında endişelenmeniz gerekmez:
- İşlemdeki bellek üzerindeki oyuncu verileri, PurchaseId geçmişini doğrulamadan önce DataStore'dan en güncel değeri almanızı gerektiriyorsa, veri içeriğinin en güncel hali olduğu için ihtiyaç da bulunmaz.
- Aynı satın alma için başka bir sunucu üzerinde geri çağırma işleminin çalışması, PurchaseId geçmişini okumak ve yazmak, ve güncellenen veri setini atomik olarak kaydetmek gerektirebilir; bu da koşul yarışları önlemek için gerekli hale gelir.
Oturum kilitleme, eğer bu sunucudaki oyuncunun DataStore'sına yazmaya yapılan bir girişim başarılı olursa, başka bir sunucunun oyuncunun DataStore'sını veri yüklenip kaydedilmeden önce başarılı bir şekilde okuyup yazamaması açısından garantiler sunar. Kısacası, bu sunucudaki bellek üzerindeki oyuncu verileri mevcut en güncel versiyondur. Bazı uyarılar vardır, fakat bunlar bu davranışı etkilemez.
Yaklaşım
ReceiptProcessor içindeki yorumlar, süreci açıklar:
Oyuncunun verilerinin bu sunucuda yüklendiğini ve hata olmadan yüklendiğini doğrulayın.
Bu sistem oturum kilitlemesini kullandıktan sonra, bu kontrol aynı zamanda bellek üzerindeki verilerin en güncel versiyonu olduğunu doğrular.
Eğer oyuncunun verileri henüz yüklenmemişse (bir oyuncu bir oyuna katıldığında beklenir), oyuncunun verilerinin yüklenmesini bekleyin. Sistem, oyuncunun verisi yüklenmeden önce oyundan çıkması durumunu da takip eder, çünkü bu geri çağırmanın yeniden aynı sunucuda bir kez daha çağrılmasını engellememeye yönelik olarak sınırsız olarak beklememesi gerektiğidir.
PurchaseId'nin oyuncu verilerinde daha önce "işlenmiş" olarak kayıtlı olmadığını kontrol edin.
Oturum kilitlemesi nedeniyle, sistemin bellekteki PurchaseIds dizisi en güncel versiyondur. Eğer PurchaseId işlemli olarak kaydedilmişse ve DataStore'da yansıtılmışsa, PurchaseGranted döndürün. Eğer kaydedilmişse ancak DataStore üzerinde YANSITILMAMIŞSA, NotProcessedYet döndürün.
Bu sunucuda oyuncu verilerini yerel olarak güncelleyin ve satın almayı "ödüllendirin".
ReceiptProcessor, genel bir geri çağırma yaklaşımını benimser ve her DeveloperProductId için farklı bir geri çağırma işlevi atar.
Oyuncu verilerini yerel olarak kaydedin ve PurchaseId'yı saklayın.
Bellek üzerindeki verileri DataStore'ya kaydetme isteği gönderin; eğer bu istek başarılıysa PurchaseGranted döndürün. Başarısız olursa, NotProcessedYet döndürün.
Eğer bu kaydetme isteği başarılı olmazsa, oyuncunun bellek üzerindeki oturum verilerini kaydetmek için ileride başarısız olabilecek bir istek halen başarılı olabilir. Bir sonraki ProcessReceipt çağrısı sırasında adım 2 bu durumu ele alacak ve PurchaseGranted döndürecektir.
Oyuncu verileri
Tekiller: PlayerData.Server, PlayerData.Client
Arka Plan
Oyun kodu için senkronize bir şekilde oyuncu oturum verilerini okuyup yazmak için bir arayüz sağlayan modüller, Roblox oyunlarında yaygındır. Bu bölüm PlayerData.Server ve PlayerData.Client'i kapsamaktadır.
Yaklaşım
PlayerData.Server ve PlayerData.Client aşağıdakileri yönetir:
- Oyuncunun verilerini belleğe yükleme; yüklemenin başarısız olduğu durumları yönetme
- Sunucu kodunun oyuncu verilerini sorgulama ve değiştirme arayüzünü sağlama
- Oyuncu verilerindeki değişikliklerin istemciye çoğaltılması, böylece istemci kodunun buna erişmesi
- Yükleme ve/veya kaydetme hatalarının istemciye çoğaltılması; böylece hata diyalogları görüntülenebilir
- Oyuncunun verilerini periyodik olarak, oyuncu oyunu terk ettiğinde ve sunucu kapandığında kaydetme
Oyuncu verilerini yükleme

SessionLockedDataStoreWrapper, veri deposuna bir getAsync isteği gönderir.
Eğer bu istek başarısız olursa, varsayılan veri kullanılır ve profil "hatalı" olarak işaretlenir; böylece ileride veri deposuna yazılmasını engeller.
Alternatif bir seçenek olarak oyuncuyu atmak vardır, ancak oyuncunun oyunun dışında bırakılmasını tercih etmek yerine, varsayılan verilerle oynamasına izin vermeyi ve ne olduğuna dair net bir aktarımla oyuncuyu bilgilendirmeyi öneririz.
Yüklenen verilerin ve hata durumunun (varsa) bulunduğu başlangıç yüklemesi PlayerDataClient'e gönderilir.
waitForDataLoadAsync ile oyuncu için bekleyen tüm threadler devam ettirilir.
Sunucu kodu için arayüz sağlama
- PlayerDataServer, aynı ortamda çalışan herhangi bir sunucu kodu tarafından gereken tekil birimlerdir.
- Oyuncu verileri, anahtarlar ve değerlerden oluşan bir sözlükte düzenlenmektedir. Bu değerleri sunucu üzerinde setValue, getValue, updateValue ve removeValue metotlarını kullanarak manipüle edebilirsiniz. Bu metotlar, asenkron bekletme olmaksızın senkronize olarak çalışmaktadır.
- Veri erişilmeden önce yüklendiğinden emin olmak için hasLoaded ve waitForDataLoadAsync yöntemleri sağlanır. Bunları, oyuncu verileriyle veri etkileşimlerinde yükleme hatalarını kontrol etme gerekliliğinden kaçınmak için, öncelikle yükleme ekranında bir kez kullanmanızı öneririz.
- Bir hasErrored yöntemi, oyuncunun ilk yüklemesi başarısız olursa varsayılan verilere yönlendirilip yönlendirilmediğini sorgulamak için kullanılabilir. Bu yöntemi kontrol edin; çünkü başarılı bir yükleme olmadan oyuncuya herhangi bir satın alma gerçekleştirmesi için izin verilmez.
- Bir playerDataUpdated sinyali, herhangi bir oyuncu verisi değiştiğinde player, key ve value ile ateşlenir. Bu sisteme abone olabilirler.
Değişikliklerin istemciye çoğaltılması
- PlayerDataServer'deki oyuncu verilerinde yapılan herhangi bir değişiklik, PlayerDataClient'e çoğaltılır; bu anahtar setValueAsPrivate ile özel olarak işaretlenmediği sürece.
- setValueAsPrivate, istemciye gönderilmemesi gereken anahtarları belirtmek için kullanılır.
- PlayerDataClient'de bir anahtarın değerini almak için (get) ve güncellendiğinde ateş edecek bir sinyal (updated) içerir. Bir hasLoaded yöntemi ve bir loaded sinyali de bulunmaktadır; böylece istemci, sistemlerini başlatmadan önce verilerin yüklenmesini ve çoğaltılmasını bekleyebilir.
- PlayerDataClient, aynı ortamda çalışan herhangi bir istemci kodu tarafından gereken tekil birimlerdir.
Hataların istemciye çoğaltılması
- Oyuncu verilerini kaydetme veya yükleme sırasında karşılaşılan hata durumları, PlayerDataClient'e çoğaltılır.
- Bu bilgilere getLoadError ve getSaveError yöntemleri ile erişilir; bunlara ek olarak loaded ve saved sinyalleri bulunur.
- İki tür hata vardır: DataStoreError ( DataStoreService isteği başarısız oldu) ve SessionLocked (bkz. Oturum Kilitleme).
- Bu olayları kullanarak istemci satın alma istemlerini devre dışı bırakın ve uyarı diyalogları uygulayın. Aşağıdaki görüntü, gösterilmesi gereken bir örnek uyarı diyalogunu göstermektedir:

Oyuncu verilerini kaydetme

Oyuncu oyundan ayrıldığında, sistem aşağıdaki adımları izler:
- Oyuncunun verilerinin veri deposuna yazılmasının güvenli olup olmadığını kontrol edin. Güvenli olamayacağı senaryolar, oyuncunun verilerinin yüklenemediği veya halen yüklenme sürecinde olduğu durumlardır.
- SessionLockedDataStoreWrapper üzerinden mevcut bellek değerini veri deposuna yazma isteği gönderir ve bu tamamlandığında oturum kilidini kaldırır.
- Oyuncunun verilerini (ve meta veri gibi diğer değişkenler ve hata durumları) sunucu belleğinden temizler.
Periyodik bir döngüde, sunucu, her oyuncunun verilerini (güvenliyse) veri deposuna kaydeder. Bu hoş bir yedeklilik, sunucu çökmesi durumunda kayba karşı koruma sağlar ve ayrıca oturum kilidinin korunması için gereklidir.
Sunucuyu kapatma isteği alındığında şu işlem BindToClose geri çağırması içerisinde gerçekleşir:
- Her oyuncunun verisini sunucuda kaydetme isteği, bir oyuncu sunucudan ayrılınca izlenen süreçle aynı şekilde yapılır. Bu istekler, BindToClose geri çağırmaları yalnızca 30 saniye içinde tamamlanacağı için paralel olarak yapılır.
- Kaydetmeleri hızlandırmak için, her anahtarın kuyruğundaki tüm diğer istekler, temel DataStoreWrapper'dan temizlenir (bkz. Yeniden Denemeler).
- Geri çağırma, tüm istekler tamamlanmadan döndürülmez.