Datastore loading half of data?

I’m having an issue with my new prototype. I made a Datastore to create and save all the PlayerStats. To make scripting less of a pain, I put all the values i needed in a folder, inside the Datastore. I simply copied it over and then use :GetAsync() to get all the values. When I load into game, I realize there are about 1-2 values that just will not save / load. If I change the name of the Datastore and test it, it actually works. But after I restart, it doesn’t work. Any thoughts on why?
script:

Script:
https://gyazo.com/4a4343693a2eb1eedea541575edeaa98

You’d be better off saving everything to a table

DataStore:SetAsync(UserId, {BackpackType = “Normal”; ToolDamage = 20})
print(DataStore:GetAsync(UserId).BackpackType)

–> "Normal

The issue with your code in particular is SetAsync doesn’t accept a tuple, it only takes 2 values. As well as that, on each line you’re doing DataStore:GetAsync for the same thing, meaning they’re just returning the same value each time (In this case, the backpack type)

You aren’t using DataStores properly, that’s why. You should probably read the documentation on DataStores again.

DataStoreService::GetDataStore
This returns a datastore (or a “database”). This is your top-level data manager. You can also specify a scope to change where it fetches from. The argument takes two values, name and scope, both of which need to be strings.

GlobalDataStore::GetAsync
GetAsync is used to retrieve a key from a datastore. All data is stored in key-value pairs. To set a value to a key, you must use a setter function (Set/UpdateAsync). This accepts one value - the key you want to retrieve.

GlobalDataStore::SetAsync
This is used to set a value to a key. You can only have one value per key, so if you intend to have multiple, you should use a table. This only accepts two arguments, key and value. Key is what the “Id” of the data you’re setting is so it can be retrieved later with GetAsync. The value is what the key holds - value of the key.

4 Likes

Thank you so much! I wish I could accept both of your answers but I’m glad you guys let me know!

https://gyazo.com/3478692282696d24e489a2f3e4d55a27

I fixed the :GetAsync(),

And to fix the :SetAsync(), I just have to make a table? Using { & }?

Yes.

GlobalDataStore:SetAsync("Key", {A = 1, B = 2, C = 3})

Okay. I came back to this thread and checked that Gyazo, since I admittedly skimmed over your response. You’re still not using GetAsync properly.

Please see the following articles:

Here are some issues I found with your code:

Let me address the bottom one first since it’s only a minor call, really. In the future when you go to retrieve that data, you don’t have anything to go by because there are no keys. All you would get is a table of values and that’s not helpful in your case. When you call GetAsync on a DataStore key, your table would look like this:

{0, 0, "string", "string", "whatever else you put in here"}

You literally won’t be able to tell what is what. You need to change that method of saving so that you actually possess keys and values in your table. This way you can index the values in the table and know what exactly it is you’re pulling from your data stores.

{A = "string", B = 0, C = "string"}

-- elsewhere
local Data = whatever:GetAsync(whatever)
print(Data.A) --> string

Okay, now that entire chunk of GetAsyncs. That, is a problem.

The first thing is that GetAsync only accepts one argument and that is the key you are trying to retrieve. You cannot put more than one argument because it’s not going to do anything. Essentially what you’ve done here is the equivalent of calling GetAsync 5 times on the same key. Due to limitations, your data store will throttle and fail to process these requests. Players will not receive their appropriate data and that is not good.

Second thing is that there is no failure redundancy here, so the data store does not go back and try again for proper data or halt saving efforts due to malformed requests. Given the way your script is set up, if a player’s data fails to load, their stats become default and then anything they earn during this time overwrites their original progress. Your inbox will explode with complaints about data loss and that’s not going to be fun at all.

Sorry to tell it to you chief, but that will need a rewrite. I cannot offer one right now since I’m writing this in place of what should’ve been focus towards an English assignment, but what I can tell you at the very least is that I encourage you to seek out a few resources and help regarding the creation of data stores.

If you are looking for an alternative solution, I recommend looking into DataStore2. It’s fine for everyone, new and advanced alike and has enough documentation for your success. You can use this while you’re looking up how to properly use DataStores. Whether you end up using this for the rest of your game or not, I still recommend reading into data stores and understanding how they work. I remember when I thought data stores were some super complex process but after a bit of reading and experimentation it became easy (and fun, minus the errors and limitations) to use.

1 Like

@colbert2677 pah I have a philosophy essay due in next 2 hours, get on my level of procrastination :sunglasses:

Players.PlayerAdded:Connect(function(player)
   local playerStats = playerStatsPrefab:Clone()
   playerStats.Parent = player 

   local playerData = DataStore:GetAsync(player.UserId)

   playerStats.ToolDamage.Value = playerData.ToolDamage
   playerStats.BackpackValue.Value = playerData.BackpackValue
end)

Players.PlayerRemoving:Connect(function(player)
   local playerStats = player.PlayerStatsObject
   local dataToSave = {
      ToolDamage = playerStats.ToolDamage.Value;
      BackpackValue = playerStats.BackpackValue.Value
   }

   DataStore:SetAsync(player.UserId, dataToSave)
end)

Okay so if there is anything you’re confused about there, like the dictionary (the thing with the set of {}), lmk.

Quick word on variable naming.

Only uses underscores _ when you’re declaring constants, i.e values like settings, which aren’t changed at run time. They should be in all caps too, for example:
MAX_PLAYERS = 12

Variables inside a function should either be in PascalCase or camelCase, but never no case. So instead of playerstats it should either be:
playerStats or PlayerStats

Outside a function its the norm to use PascaleCase and inside camelCase.

1 Like

Eh. I feel that coding conventions are an oversight that you can look beyond. Yeah there’s “proper naming” but worrying about your coding conventions doesn’t seem like the trouble unless you’re used to it or it’s on a project that demands strict adherence to a naming style. All my variables are PascalCase where possible and lowercase if not that.

Like come on, this guide says using Service in a variable name defined by a Service, or

local Var = function(args)
    -- Content
end

is bad. I’m not going to change what I’ve been used to for years because someone says that’s “the proper way”, unless it’s in, again, a project that requires that styling for organization and consistency.

OP’s gotta learn his DataStores and furthermore the API before getting fancy or interesting with concerning himself about how he names things.

Thank you all so very much, all of your replies are very helpful. It’s just starting to show that maybe video tutorials aren’t very reliable? Much of my knowledge was just ‘assumed’ because the amount of Data I am loading / saving is never addressed in a video tutorial. Thank you all so much, I have looked into Datastore2, but I really do wanna start from a base, and work my way up. I’m going to stick to the regular ol’ datastore! :slight_smile:

Some of your script I feel like is a bit incorrect, but I know what you mean. I do have one question!

Where it states:

   playerStats.ToolDamage.Value = playerData.ToolDamage
   playerStats.BackpackValue.Value = playerData.BackpackValue

Could I also do:

playerStats.ToolDamage.Value = playerData.ToolDamage.Value or 1

I want to have a default value for if there is no data saved. How would I go upon doing so?

I typically don’t rely on video tutorials because I’m a hands-on learner who needs to explore through trial, experimentation and inquiry. I’ve learned better by taking apart open source games and almost studying the API over watching tutorials. They don’t handle everything I want anyway, it’s faster if I learn it my own way. Video tutorials aren’t bad though, not what I’m implying - you can build upon that knowledge - but I wouldn’t rely on them is the thing. As a guideline they’re fine, but for learning not so much.

Glad to see you’re eager to learn about Roblox and the API. Trust me, when you learn how to use data stores at a basic level so that you don’t encounter many issues, starts becoming easy and fun.

1 Like

What you wrote is fine.

The code I wrote is correct in the sense that the API and the method is right (I hope lol). I didn’t define all my variables and stuff because it didn’t matter, when answering Qs like this, thats pretty standard practice. If I did get something actually wrong, lmk and I’ll fix!!

The “value = GetAsync or value” paradigm is slightly dangerous. No, there isn’t anything wrong with it, but later down the line you will want to switch that up. It doesn’t have to be now, just focus on getting data stores working - but later, you will want to change.

The problem with this model is that you’re relying on an automatic operator to fill your values in for you when the first one returns a nil value due to errors or no prior value being found. While this wouldn’t be an issue for if a player’s data key is blank, this would be for data store errors when it can’t retrieve a key’s value, nil or not. You always want to manually handle data failures.

If you use the “value = GetAsync or value” model, you don’t have the luxury of manually handling data errors. If your data store fails to return a value of some kind, then player data values are set to what you define after the “or” statement. When players are playing the game and then exit, their data will get overwritten by whatever they accumulate in the new session.

To get started on catching errors, you will want to wrap any of your data store calls in protected call mode (pcall). This will return a status code and any return values from the function (as GetAsync is a callback method).

local success, result = pcall(function ()
    return DS:GetAsync(key)
end)

if success then
    if result then
        -- data exists! set it!
    else
        -- data is nil! set defaults!
    end
else
    -- failed to run function! set defaults and block saving
end

Once you get your data store method in a pcall, you can handle two cases at one time: success or failure of a data store method to be used as well as whether data exists in a key or not. I’ll break it down a bit in English a bit.

if success then
    -- success code
else
    -- failure code
end

In this top-level if statement, your code is checking to see what ran in the pcall was successful. If there is an outage on data stores, methods will throw errors when they are run. You can catch these errors and work based on that bool. If the method succeeds to run, then proceed with your next steps. If not, then you should set default values and stop players from saving by adding some kind of flag to referenced later in the save function. If the flag is found, saving methods do not get called. Typically you want to retry data stores in error, but this is only intended to catch errors. I believe there is a property in the DataStoreService which allows for automatic retries but that needs to be set in the command bar and you still need to handle various cases yourself.

if result then
    -- has data
else
   -- does not have data
end

In this second if statement, this is what is checked next. This point is only reached if your data stores did not fail their call in the last if statement. Here, you can define what happens when data is either found or not. If data is found (if result then), then set their values and let them be on their merry way with playing. If not (what’s run after the else), then set default values so people have starting data to work with. In the case that you are setting defaults due to no data existing prior, you should also call a save function here just to slate their data.

And I guess that’s it! Yeah. Cool beans.

cc @PixelZombieX @DeepBlueNoSpace

(I ought to rewrite this in Community Tutorials)

3 Likes

I like to have a template table, which I then “fill in” based on what the player has stored, meaning I can delete or add entries easily.

for example

local Template = {
   Kills = 0;
   Deaths = 0;
}

Players.PlayerAdded:Connect(function(player)
   local playerSavedData = DataStore:GetAsync(player.UserId) or {}
   local gameData = {}

   for index, defaultValue in pairs(Template) do
      gameData[index] = playerSavedData[index] or defaultValue
   end
end)

Ah yes, the template table method. Helps for when you want to add or remove stats without completely resetting everyone’s progress by changing the data store due to a change in data structures. However, I must sadly inform you that the logic you are applying is also vulnerable to the problems of the “value = this or that” paradigm when it comes to data stores because it’s a variant of that “automatic operation”. I implore you to take a look.

1 Like

I was just referring to smaller things, such as:

Players.PlayerAdded:Connect(function(player)

Should be:

game.Players.PlayerAdded:Connect(function(player)

also, shouldn’t this:

 playerStats.ToolDamage.Value = playerData.ToolDamage

Be this:

 playerStats.ToolDamage.Value = playerData.ToolDamage.Value

you would define the Players variable somewhere at the top of your script. The full thing would look closer to

local Players = game.Players

--some code

Players.PlayerAdded:Connect(function(player)
   local playerSavedData = DataStore:GetAsync(player.UserId) or {}
   local gameData = {}
--some more code (hopefully!)

As for your second example, thats not quite right. Because you’re accessing a table rather than a ValueObject, there is no .Value to append.

@colbert2677 The issue you highlighted isn’t inherent to using templates, those checks are needed, just before using the template.

2 Likes