In my game I save the player’s user id once they join for the first time. This is so I can make easy edits to DataStores and keeping overall total player visit counts.
local ServerStore = game.DataStoreService:GetDataStore("ServerStore")
local IDList = ServerStore:GetAsync("IDlist")
for i, v in pairs(IDList) do
print(v)
end
This function just goes through my data and prints it.
The main thing is that the array of Ids are in a single key, which may cause problems when it goes over the 4MB limit.
local IDList = ServerStore:GetAsync("IDlist")
if not IDList then IDList = {} end
if not table.find(IDList,Player.UserId) then
table.insert(IDList,Player.UserId)
ServerStore:SetAsync("IDlist",IDList)
end
This function is the one I currently have under my ‘UnloadPlayerData’ function that connects when a player is added.
Is there a more effiecent way to store the UserIds and to add them?
How would you define efficient? Do you want to save on DataStore space? Do you want faster read/write times? What exactly are you hoping to improve on?
I don’t think you can do this natively. Roblox does track players who joined though, and that’s how erasure requests are made, but there isn’t a way to access it.
The absolute best approach would probably be to use an external database, but I would assume that if you owned an external database, you would know how to use it (I don’t so I can’t give an example for that, sorry :( ).
The next best approach would probably be to use DataStores. I wouldn’t store every user ID in a table though. Instead, I would use DataStoreV2 (not to be confused with the DataStore2.0 module), make each key the UserId, make the value true (assuming you aren’t storing any other data elsewhere). Then to view the full list, use DataStore:ListKeysAsync() or this plugin to view the full list.
So for example,
local dataStoreService = game:GetService('DataStoreService')
local players = game:GetService('Players')
local userIdDataStore = dataStoreService:GetDataStore('UserIds')
players.PlayerAdded:Connect(function(player)
local success, result = pcall(userIdDataStore.SetAsync, userIdDataStore, player.UserId, true) -- sets the key that is the player's UserId and value true
end)
Doing that would also make right to erasure requests easier than if you had them all in a single table.
If you’re already saving data elsewhere, you don’t have to call SetAsync each time the player joins. Instead, you can just use the player’s data as normal since it won’t be equal to nil unless the player hasn’t joined before.
@7z99’s reply is pretty good if you are trying to save on DataStore space.
If you store all the UserIds in one single key, there is a chance you may reach the limit and need to overflow into another key, which could make things messy.
Storing each UserId as its own key makes things easier to work with.
local ds = game:GetService("DataStoreService"):GetDataStore("PlayerVisitData")
local playervisitdata = {}
game.Players.PlayerAdded:Connect(function(player)
local key = "id_"..player
local data
local success, err = pcall(function()
data = ds:GetAsync(key)
end)
if success then
if not table.find(playervisitsdata, data) then
table.insert(playervisitdata, data)
else
print(data)
end
else
warn(err)
end
end)
game.Player.PlayerRemoving:Connect(function(player)
local key = "id_"..player
local data
local success, err = pcall(function()
data = ds:SetAsync(key, playervisitdata)
end)
if success then
print("all went well!")
else
warn(err)
end
end)
game:BindToClose(function()
wait(1)
end)
Considering how trivial it is to index the “UserId” property of a player instance there is no optimal way to store UserIds of players, instead just index that property when necessary, however, for the sake of representation the following implementation records the UserIds of every player in a dictionary (it’s faster to index a dictionary than it is to iterate over an entire array).
local Players = game:GetService("Players")
local DataStores = game:GetService("DataStoreService")
local DataStore = DataStores:GetDataStore("DataStore")
local ProtectedCall = pcall
local UserIds = {}
local function CreateStats(Player)
local Leaderstats = Instance.new("Folder")
Leaderstats.Name = "leaderstats"
Leaderstats.Parent = Player
local Visits = Instance.new("IntValue")
Visits.Name = "Visits"
Visits.Parent = Leaderstats
return true
end
local function LoadData(Player)
local Success, Result = ProtectedCall(function()
return DataStore:GetAsync(UserIds[Player])
end)
if Success then
Player.leaderstats.Visits.Value = Result or 0
else
warn(Result)
end
return true
end
local function SaveData(Player)
local Success, Result = ProtectedCall(function()
return DataStore:SetAsync(UserIds[Player], Player.leaderstats.Visits.Value)
end)
if Success then
print(Result)
else
warn(Result)
end
return true
end
local function OnPlayerAdded(Player)
UserIds[Player] = Player.UserId
local _Stats = CreateStats(Player)
repeat
task.wait()
until _Stats
local Data = LoadData(Player)
repeat
task.wait()
until Data
Player.leaderstats.Visits.Value += 1
end
local function OnPlayerRemoving(Player)
local Data = SaveData(Player)
repeat
task.wait()
until Data
UserIds[Player] = nil
end
local function OnServerShutdown()
for _, Player in ipairs(Players:GetPlayers()) do
SaveData(Player)
end
UserIds = {}
end
Players.PlayerAdded:Connect(OnPlayerAdded)
Players.PlayerRemoving:Connect(OnPlayerRemoving)
game:BindToClose(OnServerShutdown)