Data Store: Why you should get excited!
Posted on March 10, 2014 by jobro13
What’s up with the fuss in the Roblox developer world currently? Why is everyone talking about the new Data Store? What is it exactly and how can I use it? That’s exactly what this blog post is about.
Data Store vs Data Persistence: Why Data Store is better
You probably have used Data Persistence already. For those who haven’t yet: via Data Persistence you can save player data, such as the current XP the player has, or how much kills someone has made. One of the most annoying points of Data Persistence is that you have to use the API on a Player. In other words: the player has to be in game in order to save and load data! It would be incredibly handy if we also could save and load data when players are not in the game, right? Think about it for a moment. You could create global leaderboards and manage clans in-game!
This is exactly why the Roblox developers are so happy currently. Data Store allows us to save and load data, even when players are not in-game! In fact, it’s not even per-player data, but per-universe data. Yes, you are reading this right: you can even load data of other places into your place! The only prerequisite is that they have to be in the same universe. Another reason why the Data Store is awesome: if the function which saves data returns true
, you are sure that the data is saved. On Data Persistence the data would save, but if the game shut down your data would be lost. This is not the case with Data Store!
Another limitation of the Data Persistence was the limit. We could save 45000 data store units on Data Persistence. On Data Store, you have no limit on the size of the total data store, but the maximum data a key can hold is 64kb. You have access to unlimited keys though, so this should not be a limitation. Besides that, Data Store can save tables, something Data Persistence was not directly capable of. The last cool thing with Data Store is that we can finally test data saving in Roblox Studio!
How do I use the Data Store?
So, you have now probably asked yourself: How do I use it? Let’s check that out. The first thing you need to retrieve is the DataStoreService. This is, just like other services, just a member of game:
local DataStoreService = game:GetService("DataStoreService")
Now it is important to learn about the structure of a Data Store. The Roblox developers designed the API to really make your life easier. Think about the Data Store as a gigantic warehouse. This warehouse has rows of shelves and these shelves have floor levels. The Data Store has a "namespace" which represents the rows in the warehouse. Every namespace also has a scope. This represents the "floor" or column of the row. Finally, we have a key which gives us the right item.
This is extremely handy, because like this you can order your Data Store very well. Think of it for a moment. You can create a namespace for "Player Data" in which the scopes represent the actual players. The keys in this scope will then represent the data, such as XP, kills, deaths, etc. You can also create a namespace for non-player data. These could be created levels by players (which can now be accessed by everyone), statistics, chat logs…
Let’s see how we can setup some data. Via DataStore we can get the right "row and column" of the warehouse (the namespace and scope) via the method GetDataStore
. Pay attention! The first argument is the scope and the second optional argument is the namespace. If the second argument is not present, the default namespace is "global". The GetDataStore
method returns a DataStore
object, which we can use to save and load data to:
local DataStoreService = game:GetService("DataStoreService") game.Players.PlayerAdded:connect( function(Player) local PlayerStore = DataStoreService:GetDataStore(tostring(Player.userId), "PlayerData") end ) local StatisticsStorage = DataStoreService:GetDataStore("Statistics", "ServerData")
Take a look at the code. You may be wondering why the userId is used and not the name. Think of that for a moment: what happens when the player has changed his or her name..? Indeed. Their in-game name changes too. The userId stays the same though. This is important to do, because otherwise players will lose their data if they change their name!
Now, in order to save or load data, you can use several methods. The raw methods are SetAsync
and GetAsync
which respectively save and load. These functions have to be called on a DataStore
object, which we just retrieved.
local DataStoreService = game:GetService("DataStoreService") local StatisticsStorage = DataStoreService:GetDataStore("Statistics", "ServerData") local CurrentValue = StatisticsStorage:GetAsync("CurrentGameVersion") print(CurrentValue) StatisticsStorage:SetAsync("CurrentGameVersion", 20)
In above example we will get the key of the StatisticsStorage with the name CurrentGameVersion
. If this key is not present it will always return nil. Then, we set the key to 20.
SetAsync, however, will overwrite any data in the key currently. This could lead up to problems. It is better to check the old value and then write the new value, right? The API also has a function for that. This is called UpdateAsync
and it takes the key and a function to change.
local DataStoreService = game:GetService("DataStoreService") local StatisticsStorage = DataStoreService:GetDataStore("Statistics", "ServerData") local function UpdateVisits(key) if key then return key + 1 else return 1 end end StatisticsStorage:UpdateAsync("Visits", UpdateVisits)
This sample code updates the key Visits of the Statistics storage of ServerData. The function UpdateVisits
receives the old value and must return the new value. As you can see, this function also handles the situation correctly if it has not been set yet.
UpdateAsync should be the default method on updating data! This is extremely important. If two servers edit the data at the same time with SetAsync, only the server which calls this function the last gets that value stored! So, don't create your own function which seems, at first sight, to mimic the behavior of UpdateAsync! Let's take a look on how UpdateAsync really works. If you know how this work, you also know why you should use this to prevent that you wipe data which other servers have saved.
When UpdateAsync is called, you will provide a function which takes oldValue and returns newValue. Both of these arguments get posted to the roblox website, in order to try to save them. However, if the server on roblox notes that the oldValue is not the current value (meaning that the value has been updated and that thus the newValue has a high chance to be incorrect) it will tell the server to redo the UpdateAsync function. UpdateAsync will then be called with the value currently in the data saved on the roblox servers. It will then re-run your function and will again post the data to the roblox servers. However, there is a slight chance that the value has been changed again in the time that roblox notifies the server and you return the newValue! In that case, the UpdateAsync will be run again! This process keeps going until the posted oldValue is the current value in the data store. In that case, the value gets stored. If you use UpdateAsync, you can be almost sure that the data gets updated correctly without conflict!
In the case where the value is a number, you can also update the data with the IncrementAsync
function. This takes two arguments: the key, and the value which you want to increment it with. Please note that this doesn't work on values which are nil.
local DataStoreService = game:GetService("DataStoreService") local StatisticsStorage = DataStoreService:GetDataStore("Statistics", "ServerData") if not StatisticsStorage:GetAsync("Visits") then StatisticsStorage:SetAsync("Visits", 0) end StatisticsStorage:IncrementAsync("Visits", 1)
This practically does the same as the function provided earlier.
The last API member to explore is the OnUpdate
event. This event fires when a key has been updated - it’s value has changed. You could say this is the Changed
event of the DataStore. It has two arguments: the key and the function which has to be run. This function will be called with the new value.
local DataStoreService = game:GetService("DataStoreService") local StatisticsStorage = DataStoreService:GetDataStore("Statistics", "ServerData") StatisticsStorage:OnUpdate("Visits", function(NewValue) print("Visits have bene increased! The new value is: "..NewValue) end )
This will print the visits count if the visits have been increased.
There is one downside of the DataStore: the results cache. This means that if you get a key on another server, this does not necessarily mean that this is the updated key. Because of this, it is important to use the UpdateAsync method in order to make sure that you don’t lose data. The Roblox wiki also has a nice article on Data Stores.
Commentary
Leave a Comment