Is this Datastore code correct?

I tried to learn some datastore with Gemini and other DevForum users. I tried to improve my code with Gemini. I understand almost everything in the code(But i feel ashamed cuz its mostly made by Ai. Im not using it for full code but learning something new).
If you have some advices(Or whatever), tell me.

local DatastoreService = game:GetService("DataStoreService")
local PlayerTixData = DatastoreService:GetDataStore("PlayerTixData")
local players = game:GetService("Players")

local PlayerData = {}
local part = workspace:WaitForChild("Part")

local function givetix(plr, amount)
	local data = PlayerData[plr.UserId]
	if not data then return end
	
	data.Tix += amount
	print("U got" .. amount .. "! You have " .. data.Tix .. " now.")
end

players.PlayerAdded:Connect(function(plr)
	local Key = tostring(plr.UserId)
	local success, plrdata = pcall(function()
		return PlayerTixData:GetAsync(Key)
	end)
	
	if success then
		PlayerData[plr.UserId] = plrdata or {Tix = 50}
		print("You have " .. PlayerData[plr.UserId].Tix .. " tix.")
	else
		warn("Nevermind....")
	end
end)

local function SaveData(plr)
	local key = tostring(plr.UserId)
	local PlaceToSave = PlayerData[plr.UserId]
	
	if PlaceToSave then
		local success, err = pcall(function()
			PlayerTixData:SetAsync(key, PlaceToSave)
		end)

		if success then
			print("Data saved successfully!")
		else
			warn("Failed to save data: " .. err)
		end
		
		PlayerData[plr.UserId] = nil
	end
end

players.PlayerRemoving:Connect(SaveData)

game:BindToClose(function()
	for _, plr in pairs(players:GetPlayers()) do
		SaveData(plr)
	end
end)

part.Touched:Connect(function(hit)
	local plr = players:GetPlayerFromCharacter(hit.Parent)
	if not plr then return end
	
	givetix(plr, 50)
end)

This code is working well(I didnt add cooldown thing)
(Im still beginner btw)
Have a nice day/Night! : D

4 Likes

you should add exponential attempts to save and retreive data

4 Likes

there’s nothing wrong with using AI to learn imo so dont stress it
not sure why you’re handling it like this considering you could just make an int value and stick it inside the player instead of doing array indexing (as you can access the value directly from client, in comparison to having to call from client for data from the array), but yeah it should work fine.

one thing id do though is print the error that comes from the pcall() of getting datastore, so:

local success, plrdata = pcall(function()
   return PlayerTixData:GetAsync(Key)
end)

if success then
--// do thing
else
    warn("Error gathering Player Data:", plrdata)
-- You could kick the player here as well as if they play, then save,
-- the data may overlap and remove their old data. just a thought though
end

just easier to debug!

2 Likes

I’d probably use :UpdateAsync() instead of :SetAsync() for safer updates, but unless you are certainly sure, that two or more servers won’t write same field at the same time, this is absolutely fine. Also as said above, exponential retries is a good idea.

In future programming career, you could start looking into services like ProfileStore, for better data handling, versioning, etc etc.

1 Like

already got the pcall in there and the general layouts solid
what I would do though is add retrys, the way I do this myself is by using a for loop as you can break the loop on success

Ill just copy and paste my whole module so you can see. what you have is actually a pretty good usage of datastores, Theres no need to use metatables for this btw its just the method I chose to use for the way I use em

Aside from the trys main thing id say is probably pass the store as well as the key into the function
I do it this way as well, If your worried about the cost of getting stores you could run a check on the store inputted into the function to see if its string or a store
Generally speaking Have all the asyncs available as functions as well

My Data Store Module
local DataStoreModule = {}

local DSS = game:GetService("DataStoreService")

export type DataSlot = {
	__index: table?,
	[any]:any,
	["Store"] : string?,
	["Key"] : String?,
	["Value"] : any?,
	["Status"] : string?,
	Set:()->boolean?,
	Update:()->boolean?,
	Load:()->boolean?,
}

local DataSlot = {}
function DataSlot.new(Store,Key,Value) :DataSlot?
	local instance = setmetatable({
		["Store"] = Store or nil,
		["Key"] = Key or nil,
		["Value"] = Value or nil,
		["Status"] = "new"
	},{
		__index = DataSlot
	})
	return instance
end

function DataSlot:Load() : boolean?
	local LoadedData
	for i=1,5 do
		local S,E = pcall(function()
			self["Value"] = DSS:GetDataStore(self["Store"]):GetAsync(self["Key"])
		end)
		if S then
			break
		end
		task.wait(1)
	end
	if self["Value"] ~= nil then
		self["Status"] = "Loaded"
		return self["Value"]
	else
		warn('Failed to Load Data')
		self["Status"] = "Failed"
		return nil
	end
end


function DataSlot:Set()
	for i=1,5 do
		local S,E = pcall(function()
			self["Value"] = DSS:GetDataStore(self["Store"]):SetAsync(self["Key"],self["Value"])
		end)
		if S then
			return true
		end
		task.wait(1)
	end
	warn('Failed To Set Data')
	return false
end

function DataSlot:Update()
	for i=1,5 do
		local S,E = pcall(function()
			self["Value"] = DSS:GetDataStore(self["Store"]):UpdateAsync(self["Key"],self["Value"])
		end)
		if S then
			return true
		end
		task.wait(1)
	end
	warn('Failed To Update Data')
	return false
end

function DataStoreModule.GetSlot(Store,Key,Value) : DataSlot?
	return DataSlot.new(Store,Key,Value)
end

return DataStoreModule

1 Like

pretty good (but i dont understand it(reminder that i know only Datastore basics)
I also heard that i need to make some like.. save data cooldown thing (like @ChiDj123 said) and that i should use module scripts for Datastore.

Thats what i wrote by tutorial. I didnt continue it because tutorial was so difficult to understand.

local DataStoreService = game:GetService("DataStoreService")

local DataStoreVersion = 1
local AutoSave_INT = 30 

local PlayerStore = DataStoreService:GetDataStore("PlayerStore")
local session = {}

local Template = {
	Version = DataStoreVersion,
	Tix = 0
}

local module = {}

return module

I also want to use Datastores for chapters/episode saves for now. Its smt like in tmirb, Bad things or Other story games/Rpgs, Idk

using modules is a good idea, the basics really arent too far from what say im doing now
e.g

for the most part learning to code is a bit of trial and error anyway eg for datastores

first version - basic save/load functionality
Issues: Errors stop script, set only overrides and can cause some issues, data not loading and overriding

2nd version - Added Error handling and basic checks for if its not loaded and added UpdateAsync
Issues: Basically have to kick players if it doesnt load
Fixed: Errors Stopping Script, Players overriding game data if it doesnt load/save

3rd version - Adding in retrys
Issue: Can add extra usage on DSS if it keeps failing
Fixed: Kicking players

and from here like 90% of all datastores are based off this, the method or format might change a little, but the base functionality on the actual section where you save/load etc will be about this, some people have say a queue for datastores but ultimatly if you arent overusing/missusing datastores its not really needed, in the case of say repeated datastore failures with reasonable usage its usually a roblox outage which a queue system cant fix.

In terms of overusing/missusing datastores, for instance for this use case being say a story game with episodes, it comes down to how can you condense data into less stores. Eg having 1 store per episode and loading all of them will have multiple uses per player, if you had a table thats significantly less calls on DSS. Lets say you wanted stats per episode or something, you could have a indivdual store but thats when usage consideration should be accounted for, eg dont load them all at once, only load once a player clicks to display the stats for that specific episode in say a gui

for the most part your tailor it as you go along, just make sure you have the core functions for set,update,load with pcall and retrys and try to keep it to a minimum, your prob find you wont even have to consider optmising every aspect of it until you start micro managing your scripts running speeds and learning that kinda thing which is prob much further down the line in terms of luau learning

2 Likes

You can’t use Tix as this will get your moderated.

Its something like test or idk (And why? Blocktales uses tix as value and it didnt get banned).
And i wanted to learn Datastore for chapters/episode saves for now.

Nothing wrong with using AI to learn, but the way you are using it right now seems very backwards. How are you learning to actively script data store if AI is writing the code.

The way you are doing it, only makes sense if you are asking the AI to explain parts of the code it wrote that you don’t understand. But, you have to code yourself, that’s the real way you will learn.

Also what helps is saying to Ai what you think something is doing and how to use, even if you’re wrong. For example - “So update Async just updates the saved data, isn’t it basically the same as SetAsync, surely I only need to use one of them?” The AI will then respond with “no they are used for different things though they can do the same thing etc (i dont even know how data stores work I just profile store so idrk the real answer) and it will provide you with a clear explanation.

That’s how you actively learn in my opinion. Tutorials are annoying and hard to follow through if they are just telling you what to do and not explaining

1 Like

Yeah, i actually do this, ty for ur advices btw

1 Like

this isn’t true at all, there are many games that use tix as a currency

That is true and games do get banned once reported.

i dont see any mention to Tix or smt like this. That post is talking about completely different thing that doesnt connected with Tixes, idk

Maybe add a debounce to the touch.

local touchDebounce = {}


part.Touched:Connect(function(hit)
	local plr = players:GetPlayerFromCharacter(hit.Parent)
	if not plr then return end

	if touchDebounce[plr.UserId] then return end
	touchDebounce[plr.UserId] = true
	GiveTix(plr, 50) task.wait(2) --stall
	touchDebounce[plr.UserId] = nil
end)
1 Like

It would be cool! (But i had no idea how to script it. That the reason why i didnt add it)
Thank you : D