Simple Datastore Handler Tutorial

Hello Developers! I’m a fairly new addition to the ROBLOX devforums and I’ve noticed that there isn’t any “real” tutorial on the ROBLOX datastore systems. I thought I could create a simple handler myself and allow me to share it with the community and get feedback so I can improve the system. After all, life is a continuous learning process, and I myself wish to learn to make things better and give back to the community. With that being said, I wanted to create my very first tutorial on this forums featuring the datastore handler script I created. So let’s go!


Item/s to Grab:

Important Information to Note

  1. The documentation is located in the Storage Module for future reference.
  2. This is my first tutorial, so I apologize if it’s bad. Feel free to hit me up in my DMs on ways to improve this tutorial, or the materials I’ve provided. It’ll benefit me and the community!
  3. You should take this tutorial and build upon it. I hope that you could make use of what I have for you and let me know what you’ve done with it!

Documentation Expanded

Function #1

GetData
	parameters: integer (UserId)
	return: table
	purpose: Grabs a specific table that associates with the userid.

This function grabs the data for a specific user (given by their UserId). The data is expected to already have been loaded upon the player’s entrance (see function #3).

Function #2

SaveData
	parameters:	integer (UserId)
	return: table
	purpose: Saves the data to the datastore. Only used when player leaves.

This function saves the data to the ROBLOX datastore. This should only be used when the player disconnects from the session, or when the autosave runs. It returns absolutely nothing (nil) because there is no need to.

Function #3

LoadData
	parameters: integer (UserId)
	return: table
	purpose: Loads the data from the datastore, or creates a brand new one. Only used when player joins.

Loads a specific players data (given by UserId) from the ROBLOX’s datastore. If there is no data, it’ll create a fresh new data. It returns absolutely nothing (nil) and the data can be grabbed by using function #1.

Function #4

UpdateData
	parameters: table (Data to update)
	return: table
	purpose: Updates the table in storage to the edited table.

This function updates the old data with the newly edited data. The new table is sent over and the storage will update it. However, it does not save to the ROBLOX datastore (see function #2).

Function #5

RemoveData
	parameters: integer (UserId)
	return: table (old data)
	purpose: Removes a data from the datastore

This function deletes the data from the datastore.

Function #6

SaveAllData
	parameters: nil
	return: nil
	purpose: Saves all data in current game

This function will save all data that exists in the current game.

How to Edit Data

Under line 57 of the Storage module under the handler script, you should see a table that looks like this by default:

image

You can add in more information as you please. For example, if you want to add in an arrest time into the system, you could make it as such:

local tab = {
    name = player.Name,
    userId = player.UserId,
    arrestTimer = 0
}

What’s already given and examples

In the main script, the loading and the saving is already handled for you when the player joins and leaves. You can add in an auto save code if you wish just in case something fails.

To use this, you simply need to require the module and run the functions as provided in the documentation. Line 8 of the main script gives an example of requiring the module.

image

Then using the variable you defined, you can run the functions as used in the main script as well.

image


Reading the Script

In this section, I am going to attempt my best to explain what you see in this model, and what the script does, hopefully decoding it for new and novice scripters so they can learn from this, and possibly make their own system!

Let’s jump into the cake. When you first insert the model to studio, you’ll notice three different scripts: one global script, two children module scripts.
image

DatastoreHandler.lua

Lines 1-8 are comment blocks that explain where the documentation is located as well as credits and the last time the code was updated.

Line 10 gets the module using the require() function to get the module “Storage” and assigns it to a variable called storage.
image

Once the variable has been defined, we’re finally able to use the module functions, as listed in the documentation above. In this script, by default, when the script first runs, it’ll load the data for every player currently in the game (lines 13-17). Additionally, when a new player is added, it’ll load their data (lines 19-21) or when a player leaves, it’ll save their data (lines 23-25).

Lines 27 to 29 has been added in a case where the server crashes, it will push all existing data to the datastore. This code has not been tested and should not be relied upon.

image

Settings.lua

When you open this module script, all you see is a table with two keys and values. This module is used for setting the key and the version, to which the datastore service will use. This module is only called by Storage.lua that is explained after this section.

image

Storage.lua

This module script does all the fun work for you! It contains all the functions that were listed in the documentation above and runs accordingly, to which we will briefly cut down upon.

From line 43 to line 44, we have two important variables being defined. The first variable (storage) is a table where all the data of the players in-game is being held, ready for grabs with the GetData function. The second variable (settings) just grabs the table from the settings.lua module that we covered prior.

Line 46 to line 47 is where the DataStoreService is finally accessed with a unique name that was given in the settings.lua module.

image

Line 49 is a function that creates a fresh new data, and is only called upon when there’s an attempt to get the existing data of a user, but does not exist in the GetData function. Line 62 is where the new data is made and saved onto the datastore using SetAsync().

Now, we finally dig into the functions that are described in the documentation!

Line 76 is the start to the GetData() function. It is called whenever a script needs the current data of a user. It doesn’t use the datastore function, rather simply goes through the table, fetches the data, and returns a table.

Line 87 is the start to the UpdateData() function. Similarly, this function does not use the datastore function either. From the table that all the data is stored, it retrieves the old data, deletes it, and replaces with the new data that is provided in the parameter.

Line 99 is the start to the SaveData() function. This is typically called when a player leaves the server or when an auto-save function runs. This function grabs the data from the table and saves it to the datastore (line 95 to line 101). To prevent data-loss (as mentioned in the wiki), it uses the UpdateAsync function of the datastore.

Line 122 is where the LoadData() function comes in. This is typically called when a player joins the server and their data needs to be loaded. The function uses the GetAsync function of the datastore to get the players data. If there is no data, it will create a fresh new data using the createData() function we described earlier.

Line 134 is where the DeleteData() enters. This functions serves the purpose to deleting a data that the game no longer needs. It will delete the entire data and set it to nil. When GetAsync() is called upon the data after being deleted, it will then be returned as nil.

Line 141 is the start to the SaveAllData() function. This function serves to push all existing data in the game to the datastores. This is designed for auto-saves at a certain integral or when the game closes. You should not depend on this code, rather you should use the SaveData() function instead.


Update Logs

[Model Update] September 6, 2017 (16:15 PST) - The save function now uses UpdateAsync rather than SetAsync, as suggested and recommended by @Ripull.

[Forum Update] September 6, 2017 (16:47 PST) - Added a description of what is inside the scripts and explaining what they do, as suggested by @Proxidius. First tutorial, any edits/corrections/suggestions/criticisms is appreciated!

[Model & Forum Update] October 27, 2017 (15:31 PST) - Included a RemoveAsync() [DeleteAsync] to the datastore module and updated the documentation and reading the script on the forum. Some typos were fixed.

[Model & Forum Update] June 7, 2018 (00:59 PST) - Added SaveAllData() to the datastore module. Functions are now called via semicolons instead of periods (old: module.GetData(id); new: module:GetData(id)). UpdateData(), SaveData(), and LoadData() function now return a table. Documentation updated both on forum and on the script.


Thank you for allowing me to give you a tutorial on the datastore handler I’ve created. I hope that I was able to be clear enough and I am willing to take constructive criticisms. I am willing to improve on this model as well as tutorial for the better of me and everyone else. Enjoy!

54 Likes

Well, as a tutorial for your own little handler script this isn’t that bad. However as a programmer I believe you should try teaching people more how to make their own datastore systems rather than have to resort to using third party scripts. You’d likely get more praise for it too if you could pull it off better than how the roblox wiki has it already.

32 Likes

Yeah, quite frankly I wouldn’t call this a tutorial thats appropriately going over data store because all its just a script and its functions and people post these mainly in development discussion and not here.

3 Likes

It’s a nice little module. I just hope people realise that using UpdateAsync is a better method to save data than SetAsync, as it respects the previous value that existed before updating the datastore.
SetAsync is much more likely to overwrite data especially in the case of datastore outage if you don’t write your code correctly.
SetAsync should only really be used when first setting the datastore. Although it does work perfectly fine for saving, it’s just simply better to use UpdateAsync.

Most dataloss in Roblox games come from people using SetAsync too often and not UpdateAsync, I myself used to only use SetAsync.

10 Likes

Well it looks like I need to update some Datatstore systems in my game… :thinking:

Thanks for info!

1 Like

In your opinion, do you think I could explain the datastore system by using this module I’ve created? It’d be a whole new section dedicated to what each line of the code means what they do.

Do you think I should move this to #development-discussion and move it back once I put in an explanation of the datastore system as mentioned above?

I’ll update that!

You could very well use it as an example. However what you currently have written does not explain how datastore actually works, it would be a much better tutorial if you better explained your script for newer scripters visiting the forum.

Will update! Thanks for your input.

1 Like

A definite improvement over the former, good job!

2 Likes

Relised i should be using UpdateAsync instead of SetAsync Thanks for reminding me!

2 Likes

Not a bad example, but I don’t really see this as a tutorial since it’s specifically your data system.

I’m using my datastore system to explain the datastore service :stuck_out_tongue:

1 Like

Is this DataStore system still considered best practices? I like its ease of use but I notice there are no pcalls on the save and load.

I am super new to this so I am hopeful to find a system that I can understand. If this one is not keeping up with the times, I would ask if anyone knows something I am better off using.

alexnewtron published a modified version of stravant’s PlayerDataStore, which retries when the first :GetAsync fails instead of crashing and burning.

The PlayerDataStore also has a wiki article: https://www.robloxdev.com/articles/Saving-player-data-using-PlayerDataStore

kampfkarren also has DataStore2, which uses OrderedDataStores to make sure that even old saves are backed up somewhere. Afaik bereeza uses the same technique for his games. I’m personally biased against it though since I don’t like the thought of unbounded growth, but that’s just me.

2 Likes

Hi there!
My datastore module was getting a little messy, so I thought I would use a cleaner one.

Only problem is that the GetData function is returning an empty table.

-- Server Script (DatastoreHandler)
Instance.new("BindableFunction",script).Name = "getData"

script.getData.OnInvoke = function(...) 
	return storage:GetData(...) or {"NOTHING RETURNED!"} 
end

-- Command bar
p = game.Players:GetPlayers()[1]
a = game.ServerScriptService.DatastoreHandler.getData:Invoke(p.UserId) 
print(a) 
warn(unpack(a))

It seems illogical.
Am I doing something wrong?

EDIT:
I was being dumb and forgot how tables work for a second there.

I can get the values just fine :smile:

Thx for the module

Hello! Question! So… i have a lot of values i am saving, now, first of all, in the table i am indexing Number values and bool values and the like, so do i index them with .value at the end? or do i leave it for example

thingy = Parent.Child.Child.Child.ExampleValue,

for instance, as opposed to

thingy = Parent.Child.Child.Child.ExampleValue.Value

because in my previous attempts, it does not save the new values that may have changed over the course of the player playing, and i think it is because it already indexed the value. However, how do i tell it to find the new values and save them? I dont know if you understand what im getting at or not, but id appreciate the help! :slight_smile: