Is This Pet Save System Code Solid? (Loosing all player pet data occasionally)

Hey Guys, I trying to move my game into Beta and I have just one more hurdle. Making my pet saving data bulletproof.

Ok, i get it, it’s ROBLOX, but when a player says ‘I’ve lost all my pets’ I just want to cringe and hide in the corner.

Anyway, today I’m manning up and started to look through the code again, but honestly it looks fine to this noob.

So please could you run your eyes over the code to make sure I’m not missing anything. (more specifically I cringe that the SetAsync functions are overwriting existing data due to some random roblox issue)

local Players = game:GetService("Players")
local DSService = game:GetService("DataStoreService")
local data = DSService:GetDataStore("TablePets003")


Players.PlayerAdded:Connect(function(player)
	Instance.new("Folder",player).Name = "Pets"
	Instance.new("Folder",player).Name = "PetsEquiped"
	local key = player.UserId
	local Pets = player:WaitForChild("Pets")
	for i,v in pairs(game.ReplicatedStorage:WaitForChild("Pets"):GetChildren()) do
		Instance.new("IntValue",Pets).Name = tostring(v)
	end
	
	
	wait(1)
	local savedLevel = data:GetAsync(key)
	
	print("before dump")
	print(savedLevel)
	print("after dump")
	
	if savedLevel ~= nil then
		for i, data in pairs(savedLevel) do
			local pet = Pets:FindFirstChild(data[1])
			if pet then
				pet.Value = data[2]
			else
				local instance = Instance.new(data[3])
				if instance then
					instance.Name = data[1]
					instance.Value = data[2]
					instance.Parent = Pets
				end
			end
				print(data)
		end
	else
		data:SetAsync(key, {})
	end
	print(data)
end)
Players.PlayerRemoving:Connect(function(player)
	local key = player.UserId
	local savedLevel = data:GetAsync(key)
	local Pets = player:FindFirstChild("Pets")
	if not Pets then return end
	Pets = Pets:GetChildren()
	local objData = {}
	for i, obj in pairs(Pets) do
		table.insert(objData, {obj.Name, obj.Value, obj.ClassName})
	end
	if savedLevel == nil then
		data:SetAsync(key, objData)
	else
		data:UpdateAsync(key, function(oldValue)
			oldValue = objData
			return objData
		end)
	end
end)
game:BindToClose(function()
	for i, player in pairs(Players:GetPlayers()) do
		local key = player.UserId
		local savedLevel = data:GetAsync(key)
		local pets = player:FindFirstChild("Pets")
		if not pets then return end
		pets = pets:GetChildren()
		local objData = {}
		for i, obj in pairs(pets) do
			table.insert(objData, {obj.Name, obj.Value, obj.ClassName})
		end
		if savedLevel == nil then
			data:SetAsync(key, objData)
		else
			data:UpdateAsync(key, function(oldValue)
				oldValue = objData
				return objData
			end)
		end
	end
	wait(3)
end)

PS: I was hoping to convert to Datastore2 but it’s way too complicated to convert for my experience level.

Thanks in advance.

4 Likes

There’s many problems here.

  • You cannot assume that the old data was saved and destroyed when PlayerAdded runs. The player may be rejoining the same server.
  • Calls to DataStore getters and setters can fail. You need to call them in protected mode with pcall and check if they succeeded. You need to check if the data load call succeeded when you go to save player data.
  • Don’t assume that player data has completed loading when the player leaves.
  • You don’t need to do anything else when the game shuts down other than wait until the normal PlayerRemoving listeners complete.
  • You should be storing the player data in a table, not making another request to the DataStore when you want to save data.
  • Don’t store pets by themselves. All player data belongs in a single table if it can fit, with possibly some exceptions for arbitrary user-generated content data and things like that. When the player’s data is loaded, compare the version of that data (meaning the version of the template / schema the data was made from). If loaded data is newer than the current server, merge it in authoritatively, where if a key does not exist in the current server data, it is written to. If it is not newer, ignore these keys. This allows you to easily add and remove data, and support old servers.
  • Don’t save data when the player joins, there is no point.
  • Use a dictionary instead of arrays for player data.
  • Do not wait for arbitrary time in your code.

The cop-out is to just use DataStore2, which I assume gets rid of some or all of these problems. I don’t like it, it is wasteful. Writing your own code for this is easy.

4 Likes

First of all, you’re yielding in BindToClose which is not possible. This won’t have any effect on data, but having a wait(3) at the end is pointless.

Secondly, you are doing GetAsync, checking if the value is nil, then SetAsync but this is exactly what UpdateAsync is for. You should be using UpdateAsync and checking if the old value is nil, otherwise you are wasting resources for absolutely no reason.

Thirdly, you are creating the empty table, objData, and then setting the value in UpdateAsync to objData, as well as using SetAsync with objData. Instead, you should be merging oldValue with objData by looping through objData and setting each value on oldData. What you’re doing is bound to delete data if the pets object you’re looping through gets deleted, which in the case of a player leaving, or the game being shutdown this is almost guaranteed.

1 Like

Yes it is. 30 character limit bad.

1 Like

No, BindToClose waits a maximum of 30 seconds for all bound functions to finish then closes. You can’t increase the time by adding a wait.

Reading comprehension is a valuable skill.

Given a statement “X is not possible”, this means that you cannot do X. If you can in fact do X (yield in BindToClose), this means it is possible to yield in BindToClose. :slight_smile:

My bad, I thought that an error was invoked when yielding. The docs say otherwise though DataModel | Documentation - Roblox Creator Hub

Thanks for your guidance. That is a lot to take in for noob. I’ll try a to find a better way to manage this based on your points.

No wonder I have problems. It appears all the code is junk.

To be honest it’s not bad code, it just has some flaws. I struggle with datastores constantly and I’ve had 10 years of experience on Roblox, so datastores are honestly not simple to work with.

If I have to rework the code, i think i’ll try to move it Datastore2 if i can figure out how I manage saving tables in there.

It’s heartening to know this doesn’t come natural to other people. I’m still trying to get my head around coding in general.

Ok, after a week of mucking about with this code and trying to address your suggestions I’m not getting anywhere fast. So I’m thinking about sending the code to the market and putting a bounty on these fixes.
What price should I put on this piece of code? Although I am seriously thinking of requesting a Datastore2 conversion as part of the requirements.
It just needs to be robust. (not award winning code)

Your advice is appreciated. Thanking you in advance.

I’ll write a tutorial next week and PM you a link to it in Bulletin Board while it goes through Post Approval.

1 Like

wow thank-you you so much. :slight_smile: I’ll keep an eye out for it.