Datastore Resource [OLD]

[!] THIS IS AN OLD BUILD AND IS STILL USABLE, BUT NOT SUGGESTED,
GET THE NEW BUILD HERE: Datastore Resource [REMADE]

Introduction

Okay so, creating a game is hard, and one of the hard aspects of that is DataStores. Of course, if you’re just saving something like a leaderstat it’s easy, but multiple can become hard to take care of.

Why should we save data in a game?

Data is an important thing in games, it can save the ammount of coins you’ve got, the level you’re on, exp rate and much more, saving data can just make your game better to allow users to know that they can continue the game from where they left it. This can also help tycoons, simulators, or almost any game!

Okay, so what’s the resource?

If you wish to just get the resource, go further down to find the recourse below. This text is basically just explaining it and how to use it.

It’s a simple script placed in ServerScriptService which handles all the data for you. All you need to do is just change some variables, and just allow API Services in just 4 clicks. In total it would be about 6 clicks excluding you editing code.

It has a basic leaderstat creation system with it’s:

local a = Intsance.new("IntValue", leaderstatsFolder)
a.Name = "Coins"
a.Value = 10

It’s main saving logic, located in the function SetupDataSave, is located inside a table, which inside the function AddLeaderstats it retrieves and gets the data by using the saved data’s index inside of the saved table. All you need to do to save it. To save it the main saving line is in line 34,
it should say:

Server:SetAsync("-".. Client.UserId, {Tickets = Client:WaitForChild("leaderstats"):WaitForChild("Tickets").Value, }) -- It has a saved Index called 'Tickets' which is grabbed by getting the client (LocalPlayer)'s leaderstats folder and gets the *IntValue* named "Tickets"

Anyway, enough talking, you can get the resource here:

Resource (OLD)

Resource Data store - Roblox

I am no longer accepting any feedback on this due to this build being old.

You should pcall this line because it ends in “async”, which means it may fail to process.

Server:GetAsync("-".. Client.UserId)

But it will lack something except it. The key may not be registered before a new player joins. To comply with it:

local dataPushback
local success, errorMessage = pcall(function()
    dataPushback = Server:GetAsync("-" .. Client.UserId)
end)

if success then -- if it succeeded
    if dataPushback then -- if the player is not new
	    Tickets.Value = dataPushback.Tickets
    else -- if the player has not joined before
	    Tickets.Value = 0
    end
end

Also, Instance.new(_, parent) is deprecated. In this case, I recommend parenting right after the instance is created.

local leaderdata = Instance.new("Folder")
leaderdata.Name = "leaderstats"
leaderdata.Parent = Client

local Tickets = Instance.new("IntValue")
Tickets.Name = "Tickets"
Tickets.Parent = leaderdata

Hello! Thank you for your feedback. As for a pcall function, this is already implemented:

local a, b = pcall(function()
	Server:SetAsync("-".. Client.UserId, {Tickets = Client:WaitForChild("leaderstats"):WaitForChild("Tickets").Value, })
end)


But with the deprecated Instance.new() part, this will be changed.
Once again, thank you for your feedback on the resource!

As of 18/12/21 5:19 PM GMT this has been changed.

You probably didn’t get what I wanted to say.

if not Server:GetAsync("-".. Client.UserId) then Tickets.Value = 1000 return end -- Line 18
    --        ↑↑↑↑↑↑↑↑

This line is not wrapped. If you wrap with pcall and do not check the data’s presence, it will pass over the dictionary so that if the dictionary is not found, it will throw an error that says it is nil. That’s why I mentioned it here:

Oh, I seem to get what it is now. I have remade the code to fit what is going on, like so:

local Connection = game.Players.PlayerAdded
local Connection2 = game.Players.PlayerRemoving

local DataService = game:GetService("DataStoreService")
local Server = DataService:GetDataStore("Server")

local function AddLeaderstats(Client)
	local leaderdata = Instance.new("Folder")
	leaderdata.Parent = Client
	leaderdata.Name = "leaderstats"

	local Tickets = Instance.new("IntValue")
	Tickets.Parent = leaderdata
	Tickets.Name = "Tickets"

	
	local dataPushback
	
	local a, b = pcall(function()
		dataPushback = Server:GetAsync("-".. Client.UserId)
	end)
	
	if a then
		if dataPushback then
			Tickets.Value = dataPushback.Tickets
		else
			Tickets.Value = 1000
		end
	end
end

local function SetupDataSave(Client)
	local a, b = pcall(function()
		Server:SetAsync("-".. Client.UserId, {Tickets = Client:WaitForChild("leaderstats"):WaitForChild("Tickets").Value, })
	end)

	if a then
		warn(a)
	else
		warn(b)
	end
end

Connection:Connect(AddLeaderstats)
Connection2:Connect(SetupDataSave)


As of 18/12/21 5:37 PM GMT This was fixed and now works as intended.

1 Like

EDIT (12/19/2021 @ 6:05 AM EST): Forgot to parent the instances in the code sample.

A couple of things I want to point out about the code right now:

  • GetService is the canonical way to get services, please be consistent in using it when accessing services. DataStoreService is fetched using it but Players is not.

  • This code does not handle a race condition where the player joins before the script can connect to PlayerAdded. If a player joins before the connections are established they will not have data.

  • While it was correctly pointed out that using the parent argument of Instance.new is deprecated, the updated code sample did not reflect the advice given and the advice given is not in full. It’s important to understand why it’s deprecated; you should only parent after all the properties (and children, if dynamically created) have been set.

  • pcall is a callback. You don’t need an upvalue to store the value of a call, you can either return the result from within the pcall or wrap the method call directly.

  • For maintainability’s sake, please use more than one letter for a variable. “success, result” is straightforward for working with a one-returned-value callback - GetAsync returns two now though, so it should be “success, data, keyInfo” or similar.

Putting that all together, you can get something like this:

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

-- DataStores should also have proper names so it's easier to edit later!
local PlayerData = DataStoreService:GetDataStore("PlayerData")

local function playerAdded(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"

    local tickets = Instance.new("IntValue")
    tickets.Name = "Tickets"

    local success, data, keyInfo = pcall(function ()
        return PlayerData:GetAsync(tostring(player.UserId))
    end)
    if success then
        if data then
            tickets.Value = data.Tickets
        else
            tickets.Value = 1000
        end
    else
        warn("Could not fetch player data: " .. data)
        -- Add other error handling as needed here
    end

    tickets.Parent = leaderstats
    leaderstats.Parent = player
end

local function playerRemoving(player)
    -- Don't yield for this or assume WFC returns a non-nil Instance
    local tickets = player:FindFirstChild("Tickets")
    if not tickets then return end

    local data = {Tickets = tickets.Value}

    local success, err = pcall(function ()
        return PlayerData:SetAsync(tostring(player.UserId), data)
    end)
    if not success then warn(err) end
end

Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
for _, player in ipairs(Players:GetPlayers()) do
    playerAdded(player)
end

Now the other thing I want to address is the comment at the top where this resource feels the explicit need to clarify that it does not incorporate any DataStore resources.

Data saving is really cool but a large swath of developers creating these resources are dangerously obsessed with creating “easy-to-use” DataStore resources and, like I mentioned earlier, feel the explicit need to clarify that they are different from existing resources. This kind of thinking is pretty self-destructive and not helpful for developers who need deep tooling.

Let me explain: other DataStore resources like DataStore2 and ProfileService are an abstraction on using DataStores, meaning they take care of all the heavy lifting and complex programming that you ideally would want to safely handle a feature as critical as player data while your only concern is learning the developer-facing API to interface with the abstraction.

These resources provide many powerful features such as session locking, session caching, automatic saving, prototyping for easy testing of data-related features without using real data, backups and other useful tooling. Explaining what each of these means individually would defeat the purpose of the resource developer performing the research to understand what these are in order to make a better module but it’s also not the point of my response so I’ll avoid doing that.

What I’m trying to say here is, resources should not pride themselves on trying to be “simple” or not one of the existing resources because in doing that you’re effectively dismissing the level of tooling these resources provide with good reason and that’s counterproductive. Developers can miss out on useful, important or even necessary features that would expedite their development or resolve a number of issues they may have while working with their data layers by settling for resources that just “want to be different” without providing the same level of or better tooling than existing resources.

My advice to you, as well as to other developers who want to create DataStore resources, is first to think “should I?” rather than “could I?”. This comes in two parts: whether or not it would be more effective to create a usage tutorial instead for an existing resource or what you should really keep in mind when you want to create a DataStore resource.

If the problem with existing resources is that the API for a resource feels complicated, then instead of creating a new resource devoid of all the benefits of existing ones try asking for help on relevant venues to comprehend the API. You can take some time to learn it thoroughly and, if warranted, put out a resource that explains the benefits of a module and how to use it in such a way that a novice developer could understand, which may not be too difficult from a fellow novice developer.

If you’re just interested in releasing your own DataStore resource and you’re not particular about the API of existing resources, then do try to do a deep dive into what’s currently available and what kinds of features those resources offer. Ask yourself if you can provide sufficient tooling for working with DataStores much like those resources do or if your resource is too rigid and lacks feature depth. Could you not just pitch a suggestion on the resource for the developer to look at instead? Is what you have in mind for a DataStore “system” able to be integrated into any developers’ code (think: why do most DataStore resources come as modules and not scripts)? Does your resource have enough features to cover important aspects of data handling?

Existing resources don’t come the way they are for no reason. There’s a lot that should go into making the data experience - for both developers on the technical side and players on the gameplay side - the best it can be.

4 Likes

I understand, and this is going to be changed anyway, the code is very buggy and so I am remaking it right now. For now though, I do feel that the current state it is in is still usable, so it will still be open like this until the remake of it. Hopefully it will work better, and I thank you for your feedback. You do make very valid points and so I will change some of the open code until the new release of it, which will only come in a Roblox Build, as it will need to be multiple scripts to work. Thank you for your time on writing this feedback, and it is going to be helpful.

WOOHOO!
As of 19/12/21 12:16 GMT this has been remade! You can get the new build here:

or you can review the post I made about it: