Why doesn't my tool data load instantly?

so i’ve been working very recently on a datastore script for a new game, and i wondered:

i made the script entirely by myself, but i thought by using playeradded i could use that to check if the player has the datastore value = true, and then send them the item instantly.

although most of the time, when you join, it won’t appear untill you reset. any fix?

the script
local Players = game:GetService("Players")
local DSS = game:GetService("DataStoreService")

Players.PlayerAdded:Connect(function(Player)
	local BoughtValues = Instance.new("Folder")
	BoughtValues.Parent = Player
	BoughtValues.Name = "BoughtValues"
	local Hatchet = Instance.new("BoolValue")
	Hatchet.Name = "Hatchet"
	Hatchet.Parent = BoughtValues
	local HatchetData = DSS:GetDataStore("Hatchet")
	Hatchet.Value = HatchetData:GetAsync(Player.UserId) or false
	Player:WaitForChild("BoughtValues").Hatchet.Changed:Connect(function(v)
		HatchetData:SetAsync(Player.UserId, v)
		print("Data Saved")
	end)
end)
Players.PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Connect(function(Character)
		if Player.BoughtValues.Hatchet.Value == true then
			if Player.Backpack:FindFirstChild("Hatchet") == nil and Player.Character:FindFirstChild("Hatchet") == nil then
				local H = game.Lighting:FindFirstChild("Hatchet"):Clone()
				H.Parent = Player.Backpack
			end
		end
	end)
end)

game.ReplicatedStorage.BuyEvents.HatchetBuy.OnServerEvent:Connect(function(player)

	if player.leaderstats.Coins.Value >= 10 then
		player.BoughtValues.Hatchet.Value = true
	end
end)

thanks for your time/reading this!

GetAsync is a yielding function that needs to perform a web call to get the saved data, so the time in which the value is fetched is dependent on the responsiveness of the endpoint as well as how quickly the data can be transmitted between the experience server and the database.

The problem is that you’re assuming GetAsync will immediately return whatever’s in the database which is not a correct assumption to be making when working with data. It doesn’t help that you’ve chosen to separate the spawning code and the DataStore code into their own PlayerAdded connections either when it’d help to only make one.

Besides that, you have some other very scary practices in this code, like using SetAsync when a ValueBase’s Changed event is fired - NEVER do that. Keep in mind that DataStores have per-minute limitations. Find opportunities to save that are not every time a new change is made. The most common are when a player leaves and when the server closes (via BindToClose).

Other issues include:

  • Setting the parent argument before other properties. Always set up your objects before you parent them. See this PSA.

  • Lack of pcall on your DataStores. DataStores can fail so you need to be handling cases of error. Your current code is especially prone to throttling and errors and because you don’t handle those errors it can result in data loss. See the warning under SetAsync’s code sample.

  • Use tables to save player data. I can assume the trajectory of your code is to have a DataStore for every single tool; don’t do that, you’ll unnecessarily consume more DataStore requests than you reasonably should. See Best Practices for DataStores.

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local function playerAdded(player)
    -- Create folder and values
    -- Parent values to folder, do not parent folder yet
    -- pcall your DataStore, check for success and data
    -- Set respective values, parent the folder

    local function characterAdded(character)
        -- Check through their values, give them tool based on if they have it
        -- Don't use Lighting as a storage service, that's an outdated practice
    end

    player.CharacterAdded:Connect(characterAdded)
    -- If they have a character after the data fetch is done
    -- run the spawn code so they get their tools accordingly.
    if player.Character then
        characterAdded(player.Character)
    end
end

-- You can add a save function here that'll run for PlayerRemoving

Players.PlayerAdded:Connect(playerAdded)
for _, player in ipairs(Players:GetPlayers()) do
    task.spawn(playerAdded, player)
end
1 Like

wow, idk what you mean by using tables?

i understand tables, but only really on the basic level…

do you mean something like

Values = {
["Hatchet"] = {
["DATA"]
}
}

or something else? please clarify

[ please note the DATA is just there for example ]

Yes. Right now you’re only saving a boolean which is whatever the Hatchet’s value is, so instead of doing that per tool use a table to collect all of a player’s owned tools and other data.

DataStore:SetAsync("USER_ID_HERE", {
    Coins = 0,
    Foobar = 666,
    BoughtTools = {"Hatchet", "Chainsaw"}
})

Obviously don’t write it like that but it should give perspective.

Resources like ProfileService and DataStore2 are good for abstracting this workflow away from you so you only have to be concerned with collecting tools into a table and saving that table, among other benefits those resources provide. If you do want to do it by hand though you will need to do some research to get a good DataStore sample using best practice that collates most or all data into one table, one key, one DataStore.

ugh. i’m stuck, you say make values but then you say use a table for them? you can’t parent a table since it’s a object localized to the script…

could you please clarify a bit more? i’m not the best at scripting.