How do i make my datastore script efficient?

hey, so i made a datastore script… it’s works perfectly fine but peoples saying that i wrote it wrong and it can make problems in the future

my code:

local datastore = game:GetService("DataStoreService")
local ds1 = datastore:GetDataStore("EXPSaveSystem")
local ds2 = datastore:GetDataStore("GOLDSaveSystem")
local ds3 = datastore:GetDataStore("LOVESaveSystem")
local ds4 = datastore:GetDataStore("RESETSaveSystem")

function onXPChanged(plr, EXP, LOVE)
	if EXP.Value >=  (10 + 25*LOVE.Value)  then
		EXP.Value = EXP.Value - (10 + 25*LOVE.Value)
		LOVE.Value = LOVE.Value + 1
	elseif LOVE.Value == 100 then
		if EXP.Value >= LOVE.Value*9999999 then
			EXP.Value = EXP.Value - LOVE.Value*9999999
		end
	end
end
	
function onLOVEChanged(plr, EXP, LOVE)
	if LOVE.Value >= 100 then
		LOVE.Value = 100
	end
end

game.Players.PlayerAdded:connect(function(plr)
	local folder = Instance.new("Folder", plr)
	folder.Name = "GasterVoid"
	local GOLD = Instance.new("IntValue", folder)
	GOLD.Name = "GOLD"
	local EXP = Instance.new("IntValue", folder)
	EXP.Name = "EXP"
	local LOVE = Instance.new("IntValue", folder)
	LOVE.Name = "LOVE"
	LOVE.Value = 1
	local RESET = Instance.new("IntValue", folder)
	RESET.Name = "RESET"

	EXP.Value = ds1:GetAsync(plr.UserId) or 0
	ds1:SetAsync(plr.UserId, EXP.Value)
	GOLD.Value = ds2:GetAsync(plr.UserId) or 0
	ds2:SetAsync(plr.UserId, GOLD.Value)
	RESET.Value = ds4:GetAsync(plr.UserId) or 0
	ds4:SetAsync(plr.UserId, RESET.Value)
	LOVE.Value = ds3:GetAsync(plr.UserId) or 0
	ds3:SetAsync(plr.UserId, LOVE.Value)

	

 	EXP.Changed:connect(function()
    	ds1:SetAsync(plr.UserId, EXP.Value)
 		onXPChanged(plr, EXP, LOVE)
	end)
	GOLD.Changed:connect(function()
		ds2:SetAsync(plr.UserId, GOLD.Value)
	end)
	LOVE.Changed:connect(function()
		ds3:SetAsync(plr.UserId, LOVE.Value)
		onLOVEChanged(plr, EXP, LOVE)
	end)
	RESET.Changed:connect(function()
		ds4:SetAsync(plr.UserId, RESET.Value)
	end)
	
end)

so basically , as peoples said, storing different data in separate storage is unefficient…
and i have no idea how to make it better…
i have tried many ways and none of them worked like it’s suppose to work

1 Like

You could have on datastore called “playerData” and just use subtables to store the data.

local playerData = {}
local DS = game:GetService("DataStoreService"):GetDataStore("playerData")
local player = --player here

playerData["EXP"] = 500
playerData["GOLD"] = 500
playerData["LOVE"] = 500
playerData["RESET"] = 500

DS:UpdateAsync(player.UserId,function()
return playerData
end)

(that was from the top of my head, not sure if I’m 100% correct, but you get the idea)

i didn’t really understood it, how can you put specific player if the datastore works for all the players, and why you set values to 500?

Numerous events return a player, for instance, PlayerRemoving, returns a player.
Wiki Articles:
https://developer.roblox.com/en-us/articles/events
https://developer.roblox.com/en-us/api-reference/event/Players/PlayerRemoving

Data stores work in keys, you can assign a value to that key with SetAsync, and more preferrable, UpdateAsync

Wiki Articles:
https://developer.roblox.com/en-us/articles/Data-store

I used that purely for example.

When using the DataStoreService API, requests to get or set data within a Data Store should be wrapped in a pcall because the request could fail for a number of reasons. A definition of the function can be found here.

This is noted on one of the Data Store API reference pages, which states:

Functions like SetAsync() that access a data store’s contents are network calls that may occasionally fail. As shown above, it’s recommended that these calls be wrapped in pcall() to catch and handle errors.

Essentially, a pcall allows you to run your code and handle any errors which are thrown as a result of that code.

Here is an example of utilising the pcall function in terms of Data Stores:

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

-- Data Store
local MyDataStore = DataStoreService:GetDataStore("MoneyDataStore")

-- PlayerAdded Event Handler
local function PlayerAddedEvent(player)
	
	-- Set the player's money value to 100
	local success, result = pcall(MyDataStore.SetAsync, MyDataStore, tostring(player.UserId), 100)
	
	-- If success is false (the request was unsuccessful)
	if not success then
		-- Produce a warning with the details of the error
		warn(result)
	end
	
end

-- PlayerAdded Event Hook
Players.PlayerAdded:Connect(PlayerAddedEvent)
2 Likes

i use the pcall on the whole datastore? or on each stat?

if i understood correctly, is that what im suppose to do?

playerData["EXP"] = DS:GetAsync(plr.UserId) or 0

playerData["GOLD"] = DS:GetAsync(plr.UserId) or 0

playerData["LOVE"] = DS:GetAsync(plr.UserId) or 1

playerData["RESET"] = DS:GetAsync(plr.UserId) or 0

Your ordering of function calls seems like it’s probably wrong. Your onXPChanged and onLOVEChanged functions do range checks and corrections, but you’re saving the pre-corrected values to the datastores first, and a re-entrant call into the handler is saving the adjusted value. Ideally, none of your code would use ValueObjects, just Lua tables, to avoid this re-entrancy that’s calling SetAsync twice in a row in many cases (because setting EXP.Value = something is what causes EXP.Changed to fire), which is kinda of nasty and could lead to an infinite cycle type of bug if changes to the logic ever result in an xp value that isn’t stable (e.g. causes EXP.Value to flip between 2 or more values).

SetAsync itself is also a huge pitfall and the main source of player data loss when using Roblox datastores. It just overwrites the key, which means you need to be totally sure that what you’re writing is valid. For example, if the player’s data didn’t load when they joined, are they going to get all their progress reset? UpdateAsync can (mostly) solve this, but only if you structure the stored data correctly and make use of the values passed to its callback function. Using UpdateAsync the way SimplifiedCode shows is no different than SetAsync, in this regard, as it does not have any validation function.

Generally speaking, any Datastores code with no pcalls is bad Datastores code. Many things can go wrong, and you should handle ALL of the known failure codes. It’s not uncommon for a robust bit of datastores code that only manages a single, simple value, like player level, to have several hundred lines of code, not just a SetAsync() call.

1 Like

so i tried to improve it (probably did it worse xD) , well i tried i have no idea how to include the pcall function

   local playerData = {}
    local DS = game:GetService("DataStoreService"):GetDataStore("playerData")

    game.Players.PlayerAdded:connect(function(plr)
    	
    	

     local folder = Instance.new("Folder", plr)
     folder.Name = "GasterVoid"
     local GOLD = Instance.new("IntValue", folder)
     GOLD.Name = "GOLD"
     local EXP = Instance.new("IntValue", folder)
     EXP.Name = "EXP"
     local LOVE = Instance.new("IntValue", folder)
     LOVE.Name = "LOVE"
     LOVE.Value = 1
     local RESET = Instance.new("IntValue", folder)
     RESET.Name = "RESET"



    playerData["EXP"] = DS:GetAsync(plr.UserId) or 0
    playerData["GOLD"] = DS:GetAsync(plr.UserId) or 0
    playerData["LOVE"] = DS:GetAsync(plr.UserId) or 1
    playerData["RESET"] = DS:GetAsync(plr.UserId) or 0



    EXP.Changed:connect(function()
    	DS:UpdateAsync(plr.UserId,function()
    		onXPChanged(plr, EXP, LOVE)
    		return playerData["EXP"]
    	end)
    end)

    LOVE.Changed:connect(function()
    	DS:UpdateAsync(plr.UserId,function()
    	onLOVEChanged(plr, EXP, LOVE)
    	return playerData["LOVE"]
    	end)
    end)

    RESET.Changed:connect(function()
    	DS:UpdateAsync(plr.UserId,function()
    		return playerData["RESET"]
    	end)
    end)

    GOLD.Changed:connect(function()
    	DS:UpdateAsync(plr.UserId,function()
    		return playerData["GOLD"]
    	end)
    end)
    end)


    function onXPChanged(plr, EXP, LOVE)
    	if EXP.Value >=  (10 + 25*LOVE.Value)  then
    		EXP.Value = EXP.Value - (10 + 25*LOVE.Value)
    		LOVE.Value = LOVE.Value + 1
    	elseif LOVE.Value == 100 then
    		if EXP.Value >= LOVE.Value*9999999 then
    			EXP.Value = EXP.Value - LOVE.Value*9999999
    		end
    		end
    end


    function onLOVEChanged(plr, EXP, LOVE)
    	if LOVE.Value >= 100 then
    		LOVE.Value = 100
    	end
    end

Yeah something like that, not sure if you can use or though, someone will have to confirm that for me…

someone showed me another solution, much easier one , the solution called “DataStore2”, it’s also safe from exploiters

Well, nothing is completely safe from exploiters, you can use regular datastore with some sanity checks, I think that’s what DataStore2 does anyway.

Including pcall into UpdateAsync is fairly similar to SetAsync, however the only difference is you are passing an Anonymous Function as a parameter. Here is my example from above but rewritten to use UpdateAsync instead of SetAsync:

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

-- Data Store
local MyDataStore = DataStoreService:GetDataStore("MoneyDataStore")

-- PlayerAdded Event Handler
local function PlayerAddedEvent(player)
	
	-- Add 100 to the player's current money value
	local success, result = pcall(MyDataStore.UpdateAsync, MyDataStore, tostring(player.UserId), function(currentMoney)
		-- Return the new money value, which will be the current value with 100 added to it.
		-- If the money value cannot be retrieved then it will add 100 to 0 instead.
		return (currentMoney or 0) + 100
	end)
	
	-- If success is false (the request was unsuccessful)
	if not success then
		-- Produce a warning with the details of the error
		warn(result)
	end
	
end

-- PlayerAdded Event Hook
Players.PlayerAdded:Connect(PlayerAddedEvent)

I will explain the pcall function more, as I could have done that a little better before, here is an example of a function called using a pcall:

local function AddTwoNumbers(numberOne, numberTwo)
    return numberOne + numberTwo
end

local success, result = pcall(AddTwoNumbers, 1, 2)

-- success = true,
-- result = 3

You are calling the function AddTwoNumbers and supplying the parameters 1 and 2. The first returned value success is a boolean which will be true if the function call was successful, and will be false otherwise. The second returned value result will be the value which is issued by the function or if there is an error, it will be the error message.

Here is an example where the above function call would yield an error:

local function AddTwoNumbers(numberOne, numberTwo)
    return numberOne + numberTwo
end

local success, result = pcall(AddTwoNumbers, 1, "hello")

-- success = false,
-- result = [string "<eval>"]:2: attempt to perform arithmetic on local 'numberTwo' (a string value)

You can see in this case that success is false because an error was thrown by the function, because we attempted to pass a string into the function and then perform arithmetic on that string, which we cannot do. You can also see that the result value contains the error message which was raised by the function.

This allows us to do something which is known as Error Handling.

Imagine we wanted to run the AddTwoNumbers function and if there was an error, we want to warn the programmer (or user) of the issue, we would use pcall to do this! Here is an example:

local function AddTwoNumbers(numberOne, numberTwo)
    return numberOne + numberTwo
end

local success, result = pcall(AddTwoNumbers, 1, "hello")

-- If success is false
if not success then
    print("There was a problem!")
end

In a production environment, you should definitely use more descriptive error messages, but that is how errors are handled using pcalls!

1 Like