DataStores - Beginners to Advanced

If you want a safe DataStore, I highly recommend using DataStore2 or ProfileService instead.

I again want to remind you that I made this tutorial just to show you some ideas that can be applied to DataStores.

I noticed a bug in the SetUp function in the SessionLock DataStore.
The returned value will always be the value that UpdateAsync returns, so setting it to “SessionLocked” is of no use.

The changed SetUp function looks like this, now:

local function setUp(player)
	local name = player.Name
	local userId = player.UserId
	local key = "Player_" .. userId

	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"

	local cash = Instance.new("IntValue")
	cash.Name = "Cash"

	local success, data, shouldWait
	repeat
		waitForRequestBudget()
		success = pcall(dataStore.UpdateAsync, dataStore, key, function(oldData)
			oldData = oldData or default
			if oldData.SessionLock then
				--He is still sessionlocked, so just wait
				if os.time() - oldData.SessionLock < 1800 then
					--session is alive
					shouldWait = true
				else
					--session is dead, take over
					oldData.SessionLock = os.time()
					data = oldData
					return data
				end
			else
				oldData.SessionLock = os.time()
				data = oldData
				return data
			end
		end)

		if shouldWait then
			task.wait(5)
			shouldWait = false
		end
	until (success and data) or not Players:FindFirstChild(name)
	if success and data then
		cash.Value = data.Cash

		cash.Parent = leaderstats
		leaderstats.Parent = player
	end
end

I updated the tutorial accordingly, if anyone notices something else that is wrong, please tell me.

ret, data - return the same result, then why need data ?

The SetUp function in the SessionLock DataStore was changed slightly in order to fix a bug, with the new version, I think this question is resolved.

Why need this ?

for _,player in ipairs(Players:GetPlayers()) do
 coroutine.wrap(load)(player)
end

If we have this:

Players.PlayerAdded:Connect(load)

As I already mentioned in the tutorial, we use

for _,player in ipairs(Players:GetPlayers()) do
    coroutine.wrap(load)(player)
end

right after creating the functions so that even if a player joined while we were creating them (maybe because of some server delay), their data will be loaded.

A quick note
What if a player joined while we were making these functions? → Because of that, let’s iterate through the Players service and call setUp on every player before connecting our functions with a coroutine.

if the session is blocked from this cycle there will be no exit, because the save function has such a condition that does not set the session to nil

	local leaderstats = player:FindFirstChild("leaderstats")

	if leaderstats then
        end

if the session is blocked from this cycle there will be no exit, because the save function has such a condition that does not set the session to nil

Do you mean that when the player leaves while his leaderstats didn’t get created yet but his sessionlock set to true, it will stay true, “banning” him for 1800 seconds? Do I understand correctly?

In the last change in the setup function, you removed the check for 1800 seconds, now if the session is blocked, the setup function will be repeated indefinitely. Why did you remove the check for 1800 seconds from the setup function?

Oh, I had the wrong thing copied, in the tutorial, it should be correct though.
Edited my message now.

Thank you for noticing!

Going through your tutorial, it’s pretty well-made mate! I can probably see the amount of work you put into this. Though, could you tell me the theme you use for VSC? It looks very soft. But other than that, amazing job!

Thanks.
I use the One Monokai Theme which is available as extension in VSC and ColdCode to take screenshot excerpts from my code.

i have a question for you, why are you using a bindable Event in the onShutdown function
?

Question, for Berezaa’s Method, is there a way to save multiple values? Thank you!

I would put all the values into a table.

You probably shouldn’t use it anymore though, since Roblox added Versioning to Data Stores which you can use to achieve the same goal.

Once again I remind everyone that if you want a safe Data Store, you should use a module like ProfileService instead, I only created this tutorial to show some methods that can be applied to make datastoring safer.

2 Likes

The part where you call the save function (in the onShutdown part of your tutorial) you never passed through that second argument. I’m just confused on why you didn’t do that when in the previous steps above you mentioned a dontWait parameter in the function for situations where you need everything to save all at once.

Just checking but could you do this?

Local leaderstats = Instance.new("Folder", player)

and

local cash = Instance.new("IntValue", leaderstats)

You can, but you should not. Read this announcement for more information:

1 Like

Sorry for bumping, but for some reason Bez’s method refuses to work and ret is always nil.

local __ISVIP = game:GetService("ReplicatedStorage"):FindFirstChild("__ISVIP")
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local function waitForRequestBudget(requestType)
	local currentBudget = DataStoreService:GetRequestBudgetForRequestType(requestType)

	while currentBudget < 1 do
		currentBudget = DataStoreService:GetRequestBudgetForRequestType(requestType)
		task.wait(5)
	end
end
local function safeCall(playerName, func, self, requestType, ...)
	local success, ret

	repeat
		if requestType then
			waitForRequestBudget(requestType) 
		end
		success, ret = pcall(func, self, ...)

		if not success then
			print("Error: " .. ret)
			if string.find(ret, "501") or string.find(ret, "504") then
				return
			end
		end
	until (success) or (playerName and not Players:FindFirstChild(playerName))

	return success, ret
end

local function setUp(player)
	local name = player.Name
	local userId = player.UserId
	local key = "Player_" .. userId
	
	local str = Instance.new("StringValue")
	str.Name = "Codes"
	
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"

	
	local TIX = Instance.new("IntValue")
	TIX.Name = "TIX"
	
	local dataStore = DataStoreService:GetDataStore(key)
	local orderedDataStore = DataStoreService:GetOrderedDataStore(key)

	local _, pages = safeCall(name, orderedDataStore.GetSortedAsync, orderedDataStore, Enum.DataStoreRequestType.GetSortedAsync, false, 100)
	local currentPage = pages:GetCurrentPage()

	if #currentPage > 0 then
		for _, dataStoreKey in ipairs(currentPage) do
			if not Players:FindFirstChild(name) then return end
			dataStoreKey = dataStoreKey.value
			local success, ret = safeCall(name, dataStore.GetAsync, dataStore, Enum.DataStoreRequestType.GetAsync, dataStoreKey)

			if success then
				str.Value = ret
				TIX.Value = ret
				TIX.Parent = leaderstats
				str.Parent = player
				leaderstats.Parent = player
				break
			end
		end
	else
		str.Value = ""
		str.Parent = player
		TIX.Value = 100
		TIX.Parent = leaderstats
		leaderstats.Parent = player
	end
end

local function save(player, dontWait)
	if not __ISVIP.Value then
		local userId = player.UserId
		local key = "Player_" .. userId
		local leaderstats = player:FindFirstChild("leaderstats")

		if leaderstats then
			local cashValue = leaderstats.TIX.Value
			local strValue = player.Codes.Value
			local dataStore = DataStoreService:GetDataStore(key)
			local orderedDataStore = DataStoreService:GetOrderedDataStore(key)

			local _, pages = safeCall(nil, orderedDataStore.GetSortedAsync, orderedDataStore, Enum.DataStoreRequestType.GetSortedAsync, false, 1)
			local latest = pages:GetCurrentPage()[1] or 0
			local should = (type(latest) == "table" and latest.value or 0) + 1


			safeCall(nil, orderedDataStore.UpdateAsync, orderedDataStore, (not dontWait and Enum.DataStoreRequestType.UpdateAsync), should, function()
				return should
			end)
			safeCall(nil, dataStore.UpdateAsync, dataStore, (not dontWait and Enum.DataStoreRequestType.UpdateAsync), should, function()
				return cashValue,strValue
			end)
		end
	end
end

local function onShutdown()
	if __ISVIP.Value then return end
	if RunService:IsStudio() then
		task.wait(2)
	else
		local finished = Instance.new("BindableEvent")
		local allPlayers = Players:GetPlayers()
		local leftPlayers = #allPlayers

		for _,player in ipairs(allPlayers) do
			coroutine.wrap(function()
				save(player, true)
				leftPlayers -= 1
				if leftPlayers == 0 then
					finished:Fire()
				end
			end)()
		end

		finished.Event:Wait()
	end
end
Players.PlayerAdded:Connect(setUp)
Players.PlayerRemoving:Connect(save)
game:BindToClose(onShutdown)
for _, player in Players:GetPlayers() do
	coroutine.wrap(setUp)(player)
end



while true do
	task.wait(60)
	for _, player in Players:GetPlayers() do
		coroutine.wrap(save)(player)
	end
end

Im confused about “.GetAsync” in pcalls and requesting budgets. is it a function call or what?

idk nothing aboir=t datastire and i only know whats the basics. I dont understand the budgettype thin and the getordereddatastored. What does it mean?

I was trying to understand it.

But I tried everything for it to work with boolvalues but yea still does not work…

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

local dataStore = DataStoreService:GetDataStore("PlayerLocker")

local function setUp(player)
	local userId = player.UserId
	local key = userId.."_RadioEquipped"
	
	local FolderOwned = Instance.new("Folder")
	FolderOwned.Name = "ItemsOwned"
	FolderOwned.Parent = player

	local RadioOwned = Instance.new("BoolValue")
	RadioOwned.Name = "RadioOwned"
	RadioOwned.Parent = FolderOwned

	local FolderEquipped = Instance.new("Folder")
	FolderEquipped.Name = "ItemsEquipped"
	FolderEquipped.Parent = player

	local RadioEquipped = Instance.new("BoolValue")
	RadioEquipped.Name = "RadioEquipped"
	RadioEquipped.Parent = FolderEquipped

	local data = dataStore:GetAsync(key)
	
	RadioEquipped.Value = data or false
end

local function save(player)
	local userId = player.UserId
	local key = userId.."_RadioEquipped"
	local ItemsEquipped = player:FindFirstChild("ItemsEquipped")
	
	if ItemsEquipped then
		local RadioEquippedvalue = ItemsEquipped.RadioEquipped.Value
		dataStore:SetAsync(key, RadioEquippedvalue)
	end
end

local function onShutDown()
	task.wait(1)
end

game:BindToClose(onShutDown)
Players.PlayerAdded:Connect(setUp)

You didn’t add the Players.PlayerRemoving event. Also, your BindToClose is incomplete.

Sorry, I don’t understand the PCall part for the three parameters. Can someone please explain in more detail for that part?