Dice DataStore | Real-time Replication and Low-Networking DataStore

Archived


Refer to Loader instead, packed with “DataSync”, an extremely optimized version of Dice DataStore.

Dice DataStore

By Mullet Mafia Dev | Download | Source

Dice DataStore is a clean and seamless system designed to let you have the most updated information about all of your data on the client and server at every moment. Dice DataStore is meant to be used for player saving & not standalone data design, though there is a way to set a game-based datastore for things such as bans. This system was designed to create a cache file on the server and update the client with their data at an extremely low-cost networking design which will only update the client with new changes to the players datastore, allowing for an easy-access cache file you can use on the client.

Why Dice DataStore?

Dice DataStore was created for one particular reason: automated replication between client and server. This allows you, the developer, to require the DataStore module in Replicated Storage and have accurate, real time data changes on both the client and server, and even allow you to ping the server for a specific players data. All with super low networking costs.

Installation

To install Dice DataStore, grab the Roblox model here and drop it into ServerScriptService. You can also find the release on my github page here. Download the rbxmxm and drop it into your studio and then drop it into ServerScriptService.

Games using Dice DataStore

List of all games known to use Dice DataStore:

DiceDataStore Features

  • Automatic retries (stops at 5)
  • Backups
  • Prevents data over writing saves per session
  • Saves on BindToClose by default, no need to write your own code
  • Super minimal networking with packets
  • Real-time data replication
  • Globals datastore support, meaning you can have a global datastore for your entire game

Player Documentation

Change a players data and manipulate the file with a cache that replicates to the client. This is limited down to the player ID provided.

DiceDataStore:SetData

:SetData(dataStoreKey,dataStoreDictionary)

Only available on the server. Set up your default data file that players receive when they join on a key. Provide a dataStoreKey to keep track of your data. dataStoreDictionary should be a dictionary (table) for your data, allowing you to visually see what values are in your default file.

Example:

DiceDataStore:SetData('data_key',{
	['Number'] = 0;
	['Table'] = {};
	['Bool'] = false;
})

DiceDataStore:LoadData

:LoadData(userID,autoSave)

Only available on the server. When a player joins a game, you want to load their DataStore file. Call this on a PlayerAdded event with the UserId of the data you are trying to load. autoSave is a boolean value which tells the DataStore to auto save the players cached file every 5 minutes.

Returns:

dataFile --> DataStore File (will return an empty cache file if no data is found)
loadedData --> Boolean if data loaded correctly

Check loadedData on join to make sure that DataStores are working properly and if they aren’t, this will let you know and you should make player experience better by disabling purchases or preventing the player from joining the game, like Adopt Me.

DiceDataStore:SaveData

:SaveData(userID,removeAfter)

Only available on the server. Saves the current cache on file for the UserId provided, if there is nil data or the game returned false when loading data, the file will not save as it may overwrite pre-existing data. removeAfter is a boolean value, provide true if you want to remove the player file from the game, like when a player is leaving.

Returns:

dataFile --> DataStore File last saved
loadedData --> Boolean if data loaded correctly

DiceDataStore:GetData

:GetData(userID,dataFile) --> server
:GetData(dataFile) --> client
:GetData(userID,dataFile) --> optional client

If you are calling this on the server, you must require with a UserId present to grab a players data cache file. Provide dataFile as the name of a specific key in your data file if you wish to only grab a certain part of the dataFile. On the client, you do not need the UserId field OR the dataFile if you wish to grab the entire dataFile of the player. If you include dataFile only, you will only get the data of the key in your file. If you provide a UserId, the server is pinged for the most recent file of a player’s cache (if in the same server). You can optionally include a dataFile parameter to only ping the server for a specific data.

Returns:

dataFile --> either the entire dictionary OR a specific data value

Example:

DiceDataStore:GetData(Plr.UserId,'Number')

DiceDataStore:UpdateData

:UpdateData(userID,dataFile,newData)

Only available on the server. Provide a UserId to access the specific players cache and include dataFile as the specific key of the players data you wish to change. To overwrite a players current dictionary completely, simply do not include a newData parameter and only provide a dataFile parameter. This may not save unless you include ['CanSave'] in your new cache file.

Returns:

updatedFile --> the players new updated file specifically for what was called
loadedData --> returns a boolean value if the data was updated successfully

Example:

DiceDataStore:UpdateData(Plr.UserId,'Number',100)

DiceDataStore:IncrementData

:IncrementData(userID,dataFile,newNumber)

Only available on the server. IncrementData works the same as UpdateData, but allows you to update a specific key by incrementing it.

Returns:

updatedFile --> the players new updated file specifically for what was called
loadedData --> returns a boolean value if the data was updated successfully

Example:

DiceDataStore:IncrementData(Plr.UserId,'Number',100) --> add 100
DiceDataStore:IncrementData(Plr.UserId,'Number',-100) --> subtract 100

DiceDataStore:RemoveData

:RemoveData(userID)

Only available on the server. Calling this will completely remove and wipe the player datas file. This is great for GDPR requests to quickly remove a players data file from your game. Warning: this will completely wipe every save and you will not be able to recover data. Use this wisely.

DiceDataStore:WatchData

:WatchData(userID,dataFile,dataEvent) --> server
:WatchData(dataFile,dataEvent) --> client

You can watch specific data keys for changes! Bind a function this call and the function will fire every time that specific data (or the entire file) updates. Do not include dataEvent but rather include the function as the second parameter to fire that function whenever ANY and ALL data updates. Include dataFile as a specific file name to watch that data changing. This will fire whenever any data changes on the client & server.

Returns:

updatedFile --> the players new updated file specifically for what was called

Example:

-- fire whenever a specific key is changed
DiceDataStore:WatchData(Plr.UserId,'Number',function(newValue) --> server
	print('Number:',newValue)
end)
DiceDataStore:WatchData('Number',function(newValue) --> client
	print('Number:',newValue)
end)

-- fire whenever any data changes
DiceDataStore:WatchData(Plr.UserId,function(newValue) --> server
	print(newValue)
end)
DiceDataStore:WatchData(function(newValue) --> client
	print(newValue)
end)

DiceDataStore:CalculateSize

:CalculateSize(userID)

Only available on the server. Calculate roughly the size of your data file on a UserId and print the data. This uses JSON, which is similar to how DataStores are stored, but this may change and can be unreliable. Treat this as a rough estimate.

Globals Documentation

Globals is a server datastore that you can call for storing data on your game. This can be useful for things such as ban systems.

DiceDataStore:SetGlobals

:SetGlobals(dataStoreKey,dataStoreDictionary)

Only available on the server. Set your globals DataStore file the same way you call :SetData for the player default values.

Example:

DiceDataStore:SetGlobals('server_key',{
	['Bans'] = {};
})

DiceDataStore:GetGlobals

:GetGlobals(dataFile)

Calling GetGlobals will load in the most recent data every time, and does not have a cache. This means you should use this carefully to not throttle the queues (recommended amount is calling it once per player join). dataFile is an optional parameter so you can call a specific key of data.

Returns:

globalsFile --> the globals updated file specifically for what was called

Example:

game:GetService('Players').PlayerAdded:Connect(function(Plr)
	local banFile,loadedGlobals = DiceDataStore:GetGlobals('Bans')
	if loadedGlobals and not game:GetService('RunService'):IsStudio() then
		if table.find(banFile,Plr.UserId) then
			Plr:Kick('Banned')
		end
	end
end)

DiceDataStore:UpdateGlobals

:UpdateGlobals(dataFile,newData)

Only available on the server. Update a specific key of data by providing dataFile with the key and setting that data to newData, or you can overwrite the entire Globals DataStore by not including newData. Calling this will result in the DataStore automatically saving right away.

Returns:

globalsFile --> the globals updated file specifically for what was called
savedData --> boolean value if the data saved successfully

Example:

local banFile,loadedGlobals = DiceDataStore:GetGlobals('Bans')
if loadedGlobals then
	table.insert(banFile,46522586)
	DiceDataStore:UpdateGlobals('Bans',banFile) -- update with the new table changes
	local findPlr = game.Players:GetPlayerByUserId(46522586)
	if findPlr then
		findPlr:Kick('Banned')
	end
end

Example Script

Here’s a nifty little script you can use as a template

-- set up the DataStore module
local DiceDataStore = require(game:GetService('ReplicatedStorage'):WaitForChild('DiceDataStore'))
DiceDataStore:SetData('data_key',{
	['Number'] = 0;
	['Table'] = {};
	['Bool'] = false;
})

local function PlayerAdded(Plr)
	local dataFile,loadedData = DiceDataStore:LoadData(Plr.UserId,true) -- load player data
	if not loadedData then
		-- add protections in case the player data doesn't load
	end
end

game:GetService('Players').PlayerAdded:Connect(function(Plr)
	PlayerAdded(Plr)
end)
for _,Plr in pairs(game:GetService('Players'):GetPlayers()) do
	PlayerAdded(Plr)
end

game:GetService('Players').PlayerRemoving:Connect(function(Plr)
	DiceDataStore:SaveData(Plr.UserId,true) -- save player data when the player leaves
end)

Made with :heart: by Mullets_Gavin & Mullet Mafia Dev

29 Likes

I’m not trying to be rude or anything however, why should we use this over something like

ProfileService

Or

DataStore2

Which have already proven to be reliable and trust worthy

  • can you tell us what is special about this Module?
  • what are the benefits/advantages of using this Module?

Pretty sure it’s because this solves replication problems of data that’s accessible by clients and server (which can get funky if replication protocols aren’t innately embedded). When working on a game where I’m having to constantly replicate changes to clients that are done on the server, it can be pretty tedious. For less experienced programmers, that can definitely be tricky to do correctly or efficiently, so bundling this into it would be a big plus for anyone doing that.

4 Likes

I will test this now! It looks great because i can not understand any other data module, this looks clean!

1 Like

This seems pretty interesting, but I rather stick to my DataStore, but that’s beside the point, this seems like something that could be useful in the future when we get bigger servers (correct me if I’m wrong), keep up the good work and good luck! :slight_smile:

I like that there’s starting to be more competition in the datastore area. I don’t like to use these modules because I like to learn how to implement it on my own, but as someone who has been learning through all the mistakes of making my own datastore system it is nice to see the different directions people take in making their own versions. It’s also good that there are differing levels of complexity, as I can imagine this version seems a lot easier to use than others.

In a way, this is motivating to me as well seeing that something like DataStore2 or ProfileService isn’t the be-all end-all to datastores. It doesn’t hurt to use what’s available but if you want to expand your scripting knowledge, learning to make something on your own goes a long way. This here is a great example of how there isn’t one way to do things.

3 Likes

Thank you for this module. This looks really great.

I’m wondering if it’s possible to make changes and watch changes on individual values in a table. For example, if I have this table:

['skills'] = {
	mining = 5,
	farming = 2,	
	cooking = 4
}

I would like to do something like this:

:IncrementData(Plr.UserId,‘skills.mining’,2)
:WatchData(‘skills.mining’,dataEvent)

Perhaps I’m missing something but currently I have to update the entire skills table or watch for changes made on the entire table. Is there a preferred method to Increment individual values in a table like for example my skills table?

Currently there is no implementation to increment values inside of a table. You would organize your code like this:

local getData = DiceDataStore:GetData(userID,'skills')
getData.mining += 2
DiceDataStore:UpdateData(userID,'skills',getData)

-- this only needs to be called once somewhere
:WatchData(‘skills',dataEvent)

What I usually do is take advantage of the dictionary design and would simply ignore the “skills” table and just lay out my values like:

['skills_mining'] = 5;
['skills_farming'] = 2;
['skills_cooking'] = 4;

This way you can use Increment + pulling this data is still quite easy on both the client and server.

1 Like

Thank you for your detailed reply.
I’ve made some further testing and I think I might have run into a bug or maybe I’m doing something wrong.

I’m using this example in a localscript:

DiceDataStore:WatchData(function(newValue) --> client
	print(newValue)
end)

I get the error message: To watch data, use a valid Key

wow nice datastore module! i can’t wait to test this!

This is nice however when I leave the game I get the error [DICE DATASTORE]: Failed to save data, key expected, got ' string '? I’m not sure how to fix it as it is a bit vague, any help would be appreciated.