How to save data with tables and using DataStore2, or player instance folder?

Okay sorry if the title is confusing I’m pretty bad with datastores. So I just got DataStore2 to work and all I have is 2 leaderstats “Cash” and “Listeners” and it works fine. Although, I am going to need a lot more values to be saved that ALSO will being created through a player instance.

What I mean is I want a folder in the player called for instance “Collection” and the player will be able to add values to the “collection” for example a StringValue called “Disc” with the value being a song title. I want the folder to hold those anytime a player wants to add something new to the “collection”.

So is there a way to do this or save large amounts of data (I need several data values for player “Skill” level and “Ratings” etc.) through tables? I need the most efficient way possible. Sorry if this was confusing again I’m really not good or familiar with datastores.

Here is what I have for my datastore right now (I havent fully added in “Skill” yet):

local DataStore2 = require(1936396537)

DataStore2.Combine("MasterKey", "cash", "listeners", "skill")

local defaultCash = 5000
local defaultListeners = 0
local defaultskill = 1

game.Players.PlayerAdded:Connect(function(plr)
	local cashDataStore = DataStore2("cash", plr)
	local listenersDataStore = DataStore2("listeners", plr)
	
	--folders
	local leaderstats = Instance.new("Folder", plr)
	leaderstats.Name = "leaderstats"
	
	--leaderstat names
	local cash = Instance.new("IntValue", leaderstats)
	cash.Name = "Cash"
	local listeners = Instance.new("IntValue", leaderstats)
	listeners.Name = "Listeners"
	
	--cash
	local function cashUpdate(updatedValue)
		cash.Value = cashDataStore:Get(updatedValue)
	end
	
	cashUpdate(defaultCash)
	
	cashDataStore:OnUpdate(cashUpdate)
	
	--listeners
	local function listenersUpdate(updatedValue)
		listeners.Value = listenersDataStore:Get(updatedValue)
	end
	
	listenersUpdate(defaultListeners)
	
	listenersDataStore:OnUpdate(listenersUpdate)

end)

You seem to be misusing the datastore:Get() function. This is only for retrieving values, not updating them. As for that parameter in datastore:Get(), that’s the default value that the function would return. I say default because it only ever uses this value if the datastore you’re getting from isn’t holding any data. To update data, all you have to do is use datastore:Set() instead. Or, if you care about the old value and want to set it based on that, then datastore:Update().

If you want to store data in datastores, keep in mind that datastores can only hold primitive values such as booleans (true/false), numbers, and strings (text). You can also use tables with this. Judging from what you want to save, you shouldn’t have a problem with this.

If you’re extremely concerned about space, there is a way to reduce space used. Storing numbers is more cost-efficient than storing strings such as song titles. So one way is to make a dictionary mapping song titles to numbers that is the id. Then, you’d store the id instead of the song title. When you retrieve the data, you convert it back to the song title for use. Datastore2 has built-in functions to support this, datastore:BeforeSave() and datastore:BeforeInitialGet(). In BeforeSave(), you’d want to pass it a function that takes the value you want to set it to and, if there’s a song name in there, return what you want to save except converting the song name to a number. In BeforeInitialGet(), you’re retrieving the data and you’d be passed a version with the serialized number instead of the song name. You can search that same hard-coded dictionary again and find the matching song name for use which you could return.

This is the most storage-efficient way possible.

And if you’re absolutely obsessed with saving storage space (which you shouldn’t), then you should turn off the OrderedBackups setting because that consumes a lot of storage space creating backups, but it’s what’s Datastore2 is popular for. It’s popular because creating a ton of backups and consuming a lot of space is also very reliable and makes data loss very improbable.

I recommend you read the Datastore2 API for more information.

1 Like

I read the API and I saw that I can use DeforeSave() and BeforeInitialGet() like you said, but I’m still very confused how to apply it to my script.

After fixing my sciprt up a bit I made some changes and it saves the leaderstats perfectly fine right now but I still don’t understand at all how to save the “setupUserData” or “userData” table I added to the script. I also want to have players to be able to add their own imaginary song to their discography which will later be unpacked into a Gui so they can look at the song imaginary viewer stats and see the song name. Players will enter the name of the song in a gui and then it will create a new instance value in the discography data table that I tried to set up and then it will save, that’s the goal right now.

Here is the modified script from above with the new things I listed above:

local DataStore2 = require(1936396537)

DataStore2.Combine("MasterKeyTest01", "cash", "listeners", "userData")

local function setupUserData()
	local  userData = {
	
	Currency = {	
		["Skill"]= 0;
		["Gems"] = 0;
	};
		
	Discography = {
		--player has GUI that they can type their song name and then it shows up on a "discography page" GUI and the song name (and possibly song viewer stats) will be saved in theis table
	};
	
	Instrument = {
		["Type"] = {nil}; --guitar,bass,drums,keyboard
		["Model"] = {nil};
		["Color"] = {nil};
		["Material"]=  {nil};
	};
}
	return userData
end

game.Players.PlayerAdded:Connect(function(plr)
	local cashDataStore = DataStore2("cash", plr)
	local listenersDataStore = DataStore2("listeners", plr)
	local userDataStore = DataStore2("userData", plr)
	
	--folders
	local leaderstats = Instance.new("Folder", plr)
	leaderstats.Name = "leaderstats"
	
	--leaderstat/plrdata names
	local cash = Instance.new("NumberValue")
    cash.Name = "Cash"
    cash.Value = cashDataStore:Get(5000) -- The "5000" means that by default, they'll have 5000 cash
    cash.Parent = leaderstats

	local listeners = Instance.new("NumberValue")
    listeners.Name = "Listeners"
    listeners.Value = listenersDataStore:Get(0) -- The "0" means that by default, they'll have 0 listeners
    listeners.Parent = leaderstats
	
	--cash--
	cashDataStore:OnUpdate(function(newPoints)
        -- This function runs every time the value inside the data store changes.
        cash.Value = newPoints
    end)
	
	--listeners
	listenersDataStore:OnUpdate(function(newPoints)
        -- This function runs every time the value inside the data store changes.
        listeners.Value = newPoints
    end)
	
end)

--this is just to test if the stats work
game.Workspace.Tester.ClickDetector.MouseClick:Connect(function(player)
	local cashDataStore = DataStore2("cash", player)
	local listenersDataStore = DataStore2("listeners", player)
	
	cashDataStore:Increment(500)
	listenersDataStore:Increment(5)
	
	player.PlayerGui.CreateSong.Frame.Visible = true
end)

--[[EXAMPLE CODE
game.Workspace.Baseplate.ClickDetector.MouseClick:Connect(function(player)
local userData = DataStore2("userData", player)

userData["Currency"]["Gems"] = userData["Currency"]["Gems"] + 10

DataStore2("userData", player):Set(setupUserData)

print(userData["Currency"]["Gems"]) --prints the gems value (incremented by 10 every click)

end)
]]--

game.ReplicatedStorage.REvents.CreateSong.OnServerEvent:Connect(function(player,title) --I have no idea what I'm doing here lol
	wait(1)
	local userData = DataStore2("userData", player):Get(setupUserData) 
	
	table.insert(userData["Discography"], title)
	
	wait(3)
	
	DataStore2("userData", player):Set(userDataStore)
	print(userDataStore["Discography"])
end)

I also want players to be able to choose their instrument and customize the color / material (or skin id in the future) and have it save in the data table like shown above.

Note that what I mentioned above with serialization with BeforeInitialGet() and BeforeSave() is not necessary and only if you want to save storage space using the most storage-efficient method. To use them, the API page on serialization provides an example. The idea is that instead of storing very long integers or Strings (text), you can create a dictionary mapping the real value to an integer “id” that would represent them in the datastore instead and this would be more storage efficient.

If you want to save multiple things together, like instead of saving just the instrument type but also its color/skin id, I’d recommend you save it as a table in the form of {instrumentType, color}. Note that Color3s cannot be saved to datastores so you would have to serialize them by either representing them with an integer id (which saves storage space) as I talked about above or saving them as their RGB values like {instrumentType, {r, g, b}}.

Similarly, for saving multiple songs and song data together, you could save a song as a list of songs. If you want viewer stats and such to be associated with the song, you can package them together into something like {{song1, views, favorites}, {song2, views, favorites}} etc.

You can save an entire list of packaged items in a single datastore.

As for saving anything, all you have to do is use datastore:Set(newValue), which even works on tables. Other options include datastore:Increment(toAdd) for purely numerical datastores and datastore:Update(func) if you care about the old value. The parameter of Update() is a function that runs immediately and is passed the old value as the parameter. If you want to dictate what it saves based on the old value, then that function would return the new value based on the old one. An example:

-- When rewarding cash
local DataStore2 = require(1936396537)

DataStore2.Combine("MasterKeyTest01", "cash")

local cashDataStore = DataStore2("Cash", plr)
cashDataStore:Update(function(oldValue)
    if oldValue + 5 >= 5000 then -- The player has enough money already, 5000 is the maximum amount
        return 5000
    end
    return oldValue + 5
end)

Just an example; not to say you should use this if you don’t want to :stuck_out_tongue:

2 Likes

This is very helpful thank you! Another question though, for saving the songs that the player creates on the discography, would it be easier to make an actual folder in the player and when they create a new song it runs as Instance.new(“StringValue”, discography) and then to save it I would just make a for loop and save all the values inside the folder? Because that’s the only way that I understand how to make, the tables thing still kinda confuses me.

But along with the tables you mentioned above, I see how I would save it as something like {instrumentType, skinid} etc., but could you give me a small example how how you would intengrrate that into the data store script and how it would save? Also, when retrieving the table in a different script maybe, you you just “unpack” the table and then you could find the value in it?

Sorry for so many questions, again, I still get very confused with data stores and tables. :joy:

With Datastore2, you don’t have to save things manually. It’s automatically saved every single time you update the value. When you retrieve things from Datastore2, you aren’t retrieving things from the Roblox datastores but the in-memory cache. You don’t have to worry about creating your own value objects to store the data in your own cache. It’s simpler to get it straight from the datastore with datastore:Get(default) rather than value objects. And yes, if you store it as an advanced table structure, then you would look at a part of that table for that piece of information.

As for saving tables, they’re like saving any other objects.

-- When saving discography
local DataStore2 = require(1936396537)

DataStore2.Combine("MasterKeyTest01", "discography")

local discographyStore = Datastore2("discography", player) -- for some player object

-- Let's pretend for this example they're stored as a dictionary of songs mapping the name to number of likes

discographyStore:Update(function(old) -- the parameter is the old value
    old["Example Song Name"] = old["Example Song Name"] + 1
    return old
end)

In this example, we’re incrementing the number of likes for that song. The full datastore value for “discography” could look like this:

{
["Example Song Name"] = 10123,
["Example Song Name 2"] = 143,
["Example Song Name 3"] = 54
} -- etc.

If you want to include more information besides the number of likes, then you can decide what data structure you want to use.

More examples

It could also be something like

{
["Example Song Name"] = {31230, 4943, 213} -- views, likes, and favorites
}

And then to update:

-- Incrementing likes
discographyStore:Update(function(old)
    old["Example Song Name"][2] = old["Example Song Name"][2] + 1
    return old
end)

The basic premise is the same; take the old value, change it, and return the new one.

4 Likes