Here is something that may be a bit more efficient and straightforward to modify the template in the future that I made a while back. Instead of using multiple datastores it serializes two different folders (with all descendants) when saving and deserializes the data back into folders and values when it loads. To edit it go into the DataTemplate (in the script) and add any folders/values that you want inside the two existing folders. When the player data loads for the first time, whatever is in there will copy over into the player.
If you don’t want to use different code then I’d say just don’t use so many datastores. There isn’t a huge need for it. Put all the data in to one table then save the table to the userId.
local dss = game:GetService("DataStoreService");
local players = game:GetService("Players");
local rs = game:GetService("ReplicatedStorage");
local userData = dss:GetDataStore("UserData");
local dataTemplate = script:WaitForChild("DataTemplate");
local forceSaveEvent = script:WaitForChild("ForceSave");
local dtTotal = #dataTemplate:GetDescendants();
function parseData(parent, data)
if (data["obj"] == nil) then
return;
end
local contents = data["content"];
local object = Instance.new(data["obj"], parent);
object.Name = data["name"];
if (data["value"]) then
object.Value = data["value"];
end
if (contents ~= {}) then
--print(dump(contents));
for i,v in pairs(contents) do
local newTable = {obj=v.obj,content=v["content"],name=v.name};
if (v.value) then
newTable["value"] = v.value;
end
parseData(object, newTable);
end
end
return object;
end
function getTableForm(parent)
local newData = {["obj"]=parent.ClassName,["name"]=parent.Name};
local newContent = {};
if (#parent:GetChildren() > 0) then
for i,v in pairs(parent:GetChildren()) do
local tForm = getTableForm(v);
table.insert(newContent, tForm);
end
if (parent.ClassName ~= "Folder") then
newData["value"] = parent.Value;
end
newData["content"] = newContent;
return newData;
else
if (parent.ClassName ~= "Folder") then
newData["value"] = parent.Value;
end
newData["content"] = newContent;
return newData;
end
end
function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
forceSaveEvent.Event:Connect(function(player)
local lsData = getTableForm(player:WaitForChild("leaderstats"))["content"];
local pdData = getTableForm(player:WaitForChild("PlayerData"))["content"];
userData:SetAsync(player.UserId, {["leaderstats"]=lsData,["player"]=pdData});
end)
players.PlayerAdded:Connect(function(player)
local data = userData:GetAsync(player.UserId);
if (data) then
print("Load from data");
local ls = parseData(player, {["obj"]="Folder",["content"]=data["leaderstats"],["name"]="leaderstats"});
local pd = parseData(player, {["obj"]="Folder",["content"]=data["player"],["name"]="PlayerData"});
if (ls and pd) then
print("Loading successful");
end
else
print("Load defaults");
for i,v in pairs(dataTemplate:GetChildren()) do
local d = getTableForm(v);
while (d == nil) do
wait(.05);
end
parseData(player, d);
end
end
end)
players.PlayerRemoving:Connect(function(player)
local lsData = getTableForm(player:WaitForChild("leaderstats"))["content"];
local pdData = getTableForm(player:WaitForChild("PlayerData"))["content"];
userData:SetAsync(player.UserId, {["leaderstats"]=lsData,["player"]=pdData});
end)
Thanks for the reply. After working around it and understanding (ehhh… partly understanding) how it works - it saves the data correctly, but is there a way I can make new players’ data start from x?
There are some improvements to be made to this code.
I advise you write a catcher function over directly connecting to PlayerAdded. You have several yielding functions at the top of your script so there is a possibility that the function will not fire for some players. You must account for new and existing players.[1]
Use ipairs to iterate over arrays, please. It
is faster than pairs,
shows that you are working with arrays and
has more canonical basis over pairs which is designed to work with dictionaries (arbitrary keys, not necessarily non-numerical however)
Not sure what the point of dump is. Consider JSONEncode and printing the result? It’s faster than a pure-Lua solution and that kind of thing is already done for you by the engine, so might as well make use of it over reinventing the wheel.
Wrap your DataStore calls in pcalls. This helps catch errors and determine how to move forward with code. You can also prevent saving in case of DataStore errors so that player data isn’t overwritten with an unintended set, thus causing irrevocable data loss.
[1]:
local function onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in ipairs(Players:GetPlayers()) do
onPlayerAdded(player)
end
What basis are you working off of? Have you actually tested this and in which VM are you referring to? ipairs is not slower than pairs. Remember that there is a difference in the case of using pairs and ipairs not only functionally but idiomatically as well. Use case is also worth consideration.
This was also discussed at RDC, under the breakout session Lua As Fast As Possible. You can also visit the thread Faster Lua VM: Studio beta, there are several discussions around generic and numeric for loops with special consideration to the generators pairs and ipairs.
ipairs is designed to work with arrays. pairs is designed to work with dictionaries. ipairs relies on chronological numeric indexes. pairs is for scenarios where keys aren’t known from the start, which in the case of dictionaries this is such a possibility.
Internally, an ipairs iteration is intended to work at an i+1 basis. ipairs is like a numeric for loop except you can work directly with the index of the element in the array as well as its value which are returned from the generator, which is ipairs itself.
Internally, a pairs generator is intended to work with the next function for arbitrary or unknown keys, as next returns a new element in a table given a key (see the Lua Globals page for the next function). As well, in pairs, you have no guarantee of order.
The test you wrote isn’t particularly accurate because it’s not being used in a practical scenario either. If you’re able to provide an actual sample in a real production environment, I’d love to see.