Need help in optimizing my code

i want to store a lot of players data into datastore
but i dont know how to use tables to store them

i want to store all of the players data into 1 datastore (inventory, settings, coins)

image

local players = game:GetService("Players")
local rs = game:GetService("ReplicatedStorage")
local ss = game:GetService("ServerStorage")
local ok = workspace.h:FindFirstChild("Lobbies")
local dss = game:GetService("DataStoreService")
local sds = dss:GetDataStore("Settings")
local ids = dss:GetDataStore("Inventory")
local cds = dss:GetDataStore("Coins")
local scds = dss:GetDataStore("SaveCoins")
local slotds = dss:GetDataStore("Slots")

local function savesettings(plr)
	local settingsFolder = plr.Settings
	local settingss = {}
	for _, setting in next, settingsFolder:GetChildren() do
		settingss[setting.Name] = setting.Value
	end
	return settingss
end

local function loadsettings(plr, settings)
	local settingsFolder = plr.Settings
	for _, setting in pairs(settingsFolder:GetChildren()) do
		if settings[setting.Name] ~= nil then
			setting.Value = settings[setting.Name]
		end
	end
end

local function loadinv(plr, inv)
	local invFolder = plr.Inventory
	for _, item in pairs(invFolder:GetChildren()) do
		if inv[item.Name] ~= nil then
			item.Value = inv[item.Name]
		end
	end
end

local function saveinv(plr)
	local invFolder = plr.Inventory
	local invss = {}
	for _, item in next, invFolder:GetChildren() do
		invss[item.Name] = item.Value
	end
	return invss
end

local function saveslots(plr)
	local invFolder = plr.Slots
	local slotss = {}
	for _, item in next, invFolder:GetChildren() do
		if item.Value ~= nil then
			slotss[item.Name] = item.Value.Name
		end
	end
	return slotss
end

local function loadslots(plr, inv)
	local invFolder = plr.Slots
	for _, item in pairs(invFolder:GetChildren()) do
		if inv[item.Name] ~= nil then
			item.Value = plr.Inventory:FindFirstChild(inv[item.Name])
			print("got " ..inv[item.Name].. " for " ..item.Name)
		else
			item.Value = nil
		end
	end
end

players.PlayerAdded:Connect(function(player)
	warn("hi " ..player.Name)
	local val = Instance.new("ObjectValue")
	val.Name = "Player"
	val.Value = player
	val.Parent = workspace.h.What
	local plrcoins = game:GetService("ServerStorage").ChimCoins:Clone()
	plrcoins.Parent = player
	local plrInv = game:GetService("ServerStorage").InventoryPreset:Clone()
	plrInv.Name = "Inventory"
	plrInv.Parent = player
	local plrslots = game:GetService("ServerStorage").Slots:Clone()
	plrslots.Parent = player
	plrslots.Slot1.Value = plrInv.Slingshot
	plrslots.Slot2.Value = plrInv.Cake
	local plrSettings = game:GetService("ServerStorage").SettingsPreset:Clone()
	plrSettings.Name = "Settings"
	plrSettings.Parent = player
	local plrscoins = game:GetService("ServerStorage").SaveCoins:Clone()
	plrscoins.Parent = player
	local data
	local inv
	local coins
	local slots
	local scoins
	local success, errorMessage = pcall(function()
		data = sds:GetAsync(player.UserId)
		inv = ids:GetAsync(player.UserId)
		coins = cds:GetAsync(player.UserId)
		slots = slotds:GetAsync(player.UserId)
		scoins = scds:GetAsync(player.UserId)
	end)
	if success then
		if data then 
			local setting = data.settings
			loadsettings(player, setting)
		end
		if inv then
			local invs = inv.inv
			loadinv(player, invs)
		end
		if coins then
			plrcoins.Value = coins
		end
		if slots then
			local slotss = slots.slots
			loadslots(player, slotss)
		end
		if scoins then
			plrscoins.Value = scoins
		end
		print(player.Name.. "'s data loaded yayyyy")
	end
	player:LoadCharacter()
	player.Character.Parent = nil
	rs.remotes.Loaded:FireClient(player, val)
	if player.UserId == 2900845260 then
		local hi
		hi = player.Chatted:Connect(function(chat)
			if chat == "!getcoins" then
				plrcoins.Value += 28483864962
			end
		end)
	end
end)

players.PlayerRemoving:Connect(function(player)
	for i, v in pairs(workspace.h:GetDescendants()) do
		if v:IsA("ObjectValue") and v.Name == "Player" and v.Value == player then
			v:Destroy()
		end
	end
	local data = {}
	local inv = {}
	local slots = {}
	data.settings = savesettings(player)
	inv.inv = saveinv(player)
	slots.slots = saveslots(player)
	local success, message = pcall(function()
		sds:SetAsync(player.UserId, data)
		ids:SetAsync(player.UserId, inv)
		cds:SetAsync(player.UserId, player.ChimCoins.Value)
		slotds:SetAsync(player.UserId, slots)
	end)
	if success then
		print(player.Name .. "'s data was saved hooray")
	end
end)

i couldnt find an easy to understand solution
please help me out

1 Like

You should save your data in a nested table within one data store. Also, to eliminate a lot of value instances getting created, you should save this data into a module,e and then other scripts can require that module to access the data. For clients, you can use a remote function to request and send data.

Example:

local PlayerData = {
Coins = 1,
Inventory = {},
Slots = {},
Settings = {},
...
}
3 Likes

how can i implement it into my code?

1 Like

I’m not sure how your game functions so I can’t give you a solution but I can give you code you can build on. For the code I’m giving, I’d recommend using modules so it’s easier for server scripts to access player data but using events works too. The way it’s set up is that a player can request data using a remote function and they’ll get their data back (They cannot set). For the server remote function, server scripts can update a player’s data within the PlayerDataTable and also get it. This is very basic but it’s effective because there’s a retry system, eliminates the creation of multiple instances, and uses one datastore.

Code:

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

local MainStore = dss:GetDataStore("PlayerData")
local PlayerRequest:RemoteFunction; --For player data requests
local ServerRequest:BindableFunction; --For requesting data from other serverscripts

local PlayerDataTable = {}
local BaseData = { --Base data for when a player joins for the first time
	Settings = {},
	Inventory = {},
	Coins = 0,
	SaveCoins = 0,
	Slots = 1
}

--Repeat delay is for how long the function waits before retrying to get/set data
local function PlayerDataRequest(UserID:number, Get:boolean, Tries:number, RepeatDelay:number, SetData:{}?) : {}?
	if not (Tries and RepeatDelay and UserID) then warn("Check params") return end
	if Get == false and not SetData then warn("Set data not provided.") return end

	local CurrentTries = 0
	local Success, Data = nil, nil
	
	repeat
		CurrentTries += 1
		
		if Get then 
			Success, Data = pcall(MainStore.GetAsync, MainStore, UserID)
		else	
			Success, Data = pcall(MainStore.SetAsync, MainStore, UserID, SetData)
		end
		
		if Success then
			break
		else
			task.wait(RepeatDelay)
		end
		
	until (CurrentTries == Tries) or Success

	return Success, Data
end

local function DataRequest(Player:Player|number, RequestType:"Get"|"Update", Name:string?, Value:any?)
	if not Player then return end
	
	local ID;
	local Env;

	if typeof(Player) == "Instance" and Player:IsA("Player") then
		ID = Player.UserId	
		Env = "C"
	elseif typeof(Player) == "number" then
		ID = Player
		Env = "S"
	end
	
	if Env=="C" or RequestType == "Get" then return PlayerDataTable[ID] end
	
	if Env=="S" and RequestType=="Update" and typeof(Name) == "string" and Value then
		PlayerDataTable[ID][Name] = Value
		print(PlayerDataTable)
		return "Success"
	end
	
	return nil
end

local function PlayerJoin(Player:Player)
	local Success, PlayerData = PlayerDataRequest(Player.UserId, true, 3, 1)
	if not Success then warn(PlayerData) Player:Kick("Data store issues.") return end
	
	PlayerData = PlayerData and PlayerData or BaseData
	PlayerDataTable[Player.UserId] = PlayerData
	
	--Do something with player data
end

local function PlayerLeave(Player:Player)
	local PlayerData = PlayerDataTable[Player.UserId]
	if not PlayerData then return end
	
	local Success, Err = PlayerDataRequest(Player.UserId, false, 3, 1, PlayerData)
	PlayerDataTable[Player.UserId] = nil
	
	--Do additional stuff when player leaves
	print(PlayerDataTable)
end

PlayerRequest.OnServerInvoke = DataRequest
ServerRequest.OnInvoke = DataRequest

players.PlayerAdded:Connect(PlayerJoin)
players.PlayerRemoving:Connect(PlayerLeave)

If you got questions you can ask. I literally quickly wrote this in 5-7 minutes so there might be some complicated parts or even errors

2 Likes

what does this part mean, im lost at the c and s

1 Like

C = client because when they fire the event, the player gets passed first. S = server because you’ll have to fire the event with the players user id.

1 Like

also how do i update thing on the client like display coins amount and stuff like that

With thing’s like your inventory, when the player opens their inventory you could fire the remote function to get the data and use it to update their inventory UI.

As for something that constantly changes like coins, when the server remote function fires and it’s to update data, then you could sneak in a unreliable remote event that fires coin data to the player.

If you want to revert back to value instances for certain things I can show you how to implement that back into the data saving script

2 Likes

yes, show me how, im not familiar with things like that too

1 Like

I modified the code a tiny bit to include value instances for accessibility. The way it works now is everything in a table will be turned to a value instance and if this value instance changes it will be saved to the data store.
Code:

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

local MainStore = dss:GetDataStore("PlayerData")

local PlayerDataTable = {}
local BaseData = { --Base data for when a player joins for the first time
	Settings = {},
	Inventory = {},
	Misc = {Coins = 0, SaveCoins = 0},
	Slots = {},
	DataID = 0
}
local Idk = {
	number = "Number",
	boolean = "Bool",
	string = "String"
}

--Repeat delay is for how long the function waits before retrying to get/set data
local function PlayerDataRequest(UserID:number, Get:boolean, Tries:number, RepeatDelay:number, SetData:{}?)
	if not (Tries and RepeatDelay and UserID) then warn("Check params") return nil end
	if Get == false and not SetData then warn("Set data not provided.") return nil end

	local CurrentTries = 0
	local Success, Data = false, nil
	
	repeat
		CurrentTries += 1

		if Get then 
			Success, Data = pcall(MainStore.GetAsync, MainStore, UserID)
		else	
			Success, Data = pcall(MainStore.UpdateAsync, MainStore, UserID, function(OldData:DataStoreKeyInfo)
				return  if (OldData["DataID"] == SetData["DataID"]) then nil else SetData
			end)
		end
		
		if Success then
			break
		else
			task.wait(RepeatDelay)
		end

	until (CurrentTries == Tries) or Success
	
	return Success, Data
end

local function CreateInstance(InstanceName:string, Parent:Instance, Name:string,  Value:any?) : NumberValue|BoolValue|StringValue
	local X = Instance.new(InstanceName)
	X.Name = Name
	if Value then X.Value = Value end
	X.Parent = Parent
	return X
end

local function GetRandomID(Last:number) : number
	local Rand = Random.new(os.clock() + math.random(0, 1000))
	
	local Num = Rand:NextNumber(0,100)
	if Num == Last then
		Num = GetRandomID(Last)
		task.wait()
	end
	return Num
end

local function PlayerJoin(Player:Player)
	local PlrId = Player.UserId
	local Success, PlayerData = PlayerDataRequest(PlrId, true, 3, 1)
	if not Success then warn(PlayerData) Player:Kick("Data store issues.") return end
	PlayerDataTable[PlrId] = (PlayerData and PlayerData["DataID"]) and PlayerData or BaseData
	
	local Folder = CreateInstance("Folder", Player, "PlayerDataFolder")
	
	for SectionName, Data in PlayerDataTable[PlrId] do
		if typeof(Data) ~= "table" then continue end

		local SubFolder = CreateInstance("Folder", Folder, SectionName)
		
		for Name, Value in Data do
			local Type = Idk[typeof(Value)]
			if not Type then continue end
			
			local Val = CreateInstance(`{Type}Value`, SubFolder, Name, Value)
			
			Val.Changed:Connect(function(ChangedValue: number) 
				local OldID = PlayerDataTable[PlrId]["DataID"]
				local NewId = GetRandomID(ChangedValue, OldID)
				
				PlayerDataTable[PlrId]["DataID"] = NewId
				PlayerDataTable[PlrId][SectionName][Name] = ChangedValue
			end)
		end
	end
end

local function PlayerLeave(Player:Player)
	local PlayerData = PlayerDataTable[Player.UserId]
	if not PlayerData then return end
	
	local Success, Err = PlayerDataRequest(Player.UserId, false, 3, 1, PlayerData)
	if Err then warn(Err) end
	PlayerDataTable[Player.UserId] = nil
end

players.PlayerAdded:Connect(PlayerJoin)
players.PlayerRemoving:Connect(PlayerLeave)

Using base data as an example… the code would loop through their data and find any tables like misc. After, values under misc that are of type string, number, or boolean will get turned into instances. The rest of the code detects when these values change and then updates the player’s session data. Btw don’t remove the DataID part as it’s important for knowing whether the player’s data changed when saving. Like last time, if you have questions you’re free to ask :smile:

2 Likes

ive been trying to make the code easy to understand but there are some errors


Screenshot 2025-02-20 141721

1 Like

The data table you’re saving needs to include DataID if you want to prevent re-writing the same data. You could also remove the check if you don’t care…

Btw, can you print out your data when you load in? Also, do the other parts of the code work?

2 Likes

only that part, but it needs to work to save the data
oh and this part too, but it doesnt throw an error
Screenshot 2025-02-20 151519

1 Like

This is such an unproductive font

3 Likes

also how do i print tables (im not familiar with tables sorry)

1 Like

What I did, was just

print(PlayerDataTable)

That prints the table

1 Like

@sonic_848 Hey, quick question—do I need to add anything else to make this work? Like, do I have to manually create the instances myself?
I don’t see any items inside my PlayerDataTable when I check in Explorer. But when I print the table, it does show that the objects and values are there. The weird thing is, I just can’t see them in Explorer, so I can’t update them.
I also have a button that’s supposed to add +1 click when pressed, but it’s not working. Any idea what I might be missing? If you want I can send my script?

1 Like

oh that works?? last time i tried and it printed some random letters

1 Like

I did this:

local function PlayerJoin(Player:Player)
	local PlrId = Player.UserId
	local Success, PlayerData = PlayerDataRequest(PlrId, true, 3, 1)
	if not Success then warn(PlayerData) Player:Kick("Data store issues.") return end
	PlayerDataTable[PlrId] = (PlayerData and PlayerData["DataID"]) and PlayerData or BaseData

	local Folder = CreateInstance("Folder", Player, "PlayerDataFolder")

	for SectionName, Data in PlayerDataTable[PlrId] do
		if typeof(Data) ~= "table" then continue end

		local SubFolder = CreateInstance("Folder", Folder, SectionName)

		for Name, Value in Data do
			local Type = DataTypes[typeof(Value)]
			if not Type then continue end

			local Val = CreateInstance(`{Type}Value`, SubFolder, Name, Value)

			Val.Changed:Connect(function(ChangedValue: number) 
				local OldID = PlayerDataTable[PlrId]["DataID"]
				local NewId = GetRandomID(ChangedValue, OldID)

				PlayerDataTable[PlrId]["DataID"] = NewId
				PlayerDataTable[PlrId][SectionName][Name] = ChangedValue
			end)
		end
	end
	
	print(PlayerDataTable) -- Added table here
end

So exactly like @sonic_848 but just added the print in the end

1 Like

you should use profilestore. it fixes most of the problems that roblox’s datastore service has! tutorial:
https://m.youtube.com/watch?v=evBhoqeYegQ&pp=ygUNcHJvZmlsZSBzdG9yZQ%3D%3D

1 Like