Suphi's DataStore Module

Is this still maintanance? I’ll using this for my new project game

If you find a problem tell me and ill fix it

1 Like

Hey, nice module - works really well however one issue I have is that you mention that this module reconciles missing data, but does this handle deep copying tables?
From my testing, data reconciles if it is at the first level but going deeper into a table it no longer does. Would make a massive improvement if you could implement this natively.
Thanks for making this!

Hi Suphi, I am having this problem where (just out of the blue after updating), where whenever I open my Data Script and your Data Module, my studio crashes. It doesn’t crash with any other datastores. I double checked, no circular dependencies, no unlimited loops without any exits - which shouldn’t matter anyways because I am still in edit, not play test. Any idea whats going on?

it is a deep reconcile here are the functions

Clone = function(original)
	if type(original) ~= "table" then return original end
	local clone = {}
	for index, value in original do clone[index] = Clone(value) end
	return clone
end

Reconcile = function(target, template)	
	for index, value in template do
		if type(index) == "number" then continue end
		if target[index] == nil then
			target[index] = Clone(value)
		elseif type(target[index]) == "table" and type(value) == "table" then
			Reconcile(target[index], value)
		end
	end
end

are you using a mac or windows?

Does studio crash when you run the game or just by simply opening the module while the game is not running?

if studio is crashing outside of game then its most likely related to this bug

I did some testing and I think the issue arises when trying to reconcile a list of items where the index is a number.

An example of something that does not reconcile would be

[1] = {
   outfit = {
      hat = 1,
      shirt = 1,
      pants = 1,
   },
}

Where I want to add a new coins = 0 field after outfit - so the new template would be

[1] = {
   outfit = {
      hat = 1,
      shirt = 1,
      pants = 1,
   },
   coins = 0,
}

This does not reconcile correctly because the Reconcile function continues without recursively calling itself back to go deeper into the table if the key is a number.
Currently, I iterate over each individual list item and reconcile them individually but it would be nice if you could implement support for this.

I know that other data reconciling functions only reconcile data behind string keys, but perhaps adding an option to reconcile integer keys could be useful.

in the reconcile function simply comment out or remove the number type check line

Reconcile = function(target, template)	
	for index, value in template do
		if type(index) == "number" then continue end -- comment or remove this line out
		if target[index] == nil then
			target[index] = Clone(value)
		elseif type(target[index]) == "table" and type(value) == "table" then
			Reconcile(target[index], value)
		end
	end
end
1 Like


Why is this occuring? I’m using the datastore module with save slot scopes. When I shut down, some players only get 1 and then this bug happens.

These are internal Roblox errors as long as a player does not get 15 of these errors in a row you don’t normally have to worry about it even if they do get 15 memorystore errors in a row they wont lose any data all that will happen is the session will change state to closed/false

I believe your slot problem is related to something else

Any fix??? Maybe I have to continue calling the new() method to get the data???

Players get that error more than 15 sometimes… It kinda started after I did soft shutdowns and players in new servers sometimes don’t see their slots. Overall the bug is gamebreaking. I loaded three datastores of the same player (but in diff scopes) in a for loop with no interval waits. Could that be why? Do I need to add a wait interval?

It kinda only happens in private servers…

My first impressions of this module are great, awesome upsides to ProfileService. However, I do have a few questions.

  1. With regard to local caching, if a player is on another server and they are using a local cache of their data. Does it become available if you force :Save() against their data?

  2. As for queues, I just want to ensure I understand correctly. For the built-in system, it can only store info for up to 45 days before it will automatically clear, correct? Curious what the reasoning for this is.

  3. The final thing I am curious about is whether or not it will be known that a user’s data is loaded on another server? I don’t believe so by looking over the code, however I am unsure.

For background, I am working for an RP game with arrest and economy functions. Currently, I am working on the ability for people to receive payment while they are either not in the same server, or issue arrests by the courts to players not in the game.

  1. while the datastore state is true you can be 100℅ certain that the catched data is most recent if the state is not true then the catched data might not be upto data so that information should not be 100℅ trusted as being most recent
    If another server does read() it will get the value of the last save this will happen every 30 seconds or if you call save()

  2. that is correct that is a limitation of robloxs memorystore

  3. if the state is open in another server when you try to open that same key in another server it will respond with locked

hey, im currently learning how to use the module–coming off of datastore2 there are some questions

Player, "Inventory", {"Some", ["index"]={} }

when using set & saving, printing Inventory will only net [1] = “Some” as the output, “index” = {} is missing. its not problematic I just found it to be an odd behavior–its like the table has to have some value inside it in order to persist


using the bare minimum of getasyncs and setasyncs–is this adequate i.e. 1 getasync is used when we initially call .new right on playeradded, and if we have certain configurations, using save on destroy() would suffice enough so that essentially 1 setasync and getasync is all that is needed

im used to the above on ds2 and hadnt really needed to worry about it, it would getasync on the first get and cache it, setasync on the playerremoving just wondering if this is reliable


backups arent explicitly defined but i believe you gave some nods towards how to do it & im also taking information of what i know from ds2, does this look right? (used for rollingback data if necessary)

game.Players.PlayerRemoving:Connect(function(player)
	local dataStore = DataStoreModule.find("Player", player.UserId)
	local storedValue = dataStore.Value
	
	if dataStore ~= nil then dataStore:Destroy() end -- If the player leaves datastore object is destroyed allowing the retry loop to stop
	
	local ostime = os.time()
	
	
	--local BackupStore = DataStoreModule.new("Player", player.UserId .. ":" .. ostime)
	local BackupStore = DataStoreModule.new("Backups", player.UserId .. ":" .. ostime)

	BackupStore:Open()
	
	BackupStore.Value = storedValue
	
	local response = BackupStore:Save()
	
	print(response)
	if response == "Saved" then
		
		local orderedDataStore = game:GetService("DataStoreService"):GetOrderedDataStore(player.UserId .. ":Backups")
		print("succ")
		--add to ordered datastore for this player that this backup ds exists.
		orderedDataStore:SetAsync(ostime, 1)
		
		
		
	else
		print("fail")
	end
	
	BackupStore:Destroy()
	
end)

if we wanted to clean up and remove old backupdata, are we able to use some sort of removeasync in the module?

Roblox does no allow mixed tables to be saved into the datastore

datastore.Value = {
    [1] = "A",
    [2] = "B",
    [3] = "C",
    ["Index1"] = "D",  -- this will not be saved
    ["Index2"] = "E",  -- this will not be saved
}
-- everything will not save correctly because this is a array + dictionary
-- only the array part will save
datastore.Value = {
    ["1"] = "A",
    ["2"] = "B",
    ["3"] = "C",
    ["Index1"] = "D",
    ["Index2"] = "E",
}
-- everything will now save correctly because this is only a dictionary
-- there is no array part

Roblox will automatically save a backup for 30 days you can read about it here Data Stores | Documentation - Roblox Creator Hub
to access this you can do

local DataStoreService = game:GetService("DataStoreService")
local playerStore = DataStoreService:GetDataStore("Player")

local success, pages = pcall(playerStore.ListVersionsAsync, playerStore, player.UserId)
if success == false then error("Failed to get versions") end
local items = pages:GetCurrentPage()
for key, info in items do
    print("Key:", key, "; Version:", info.Version, "; Created:", info.CreatedTime, "; Deleted:", info.IsDeleted)
end

but if if you want to save your own version then you can do this

local DataStoreModule = require(11671168253)
local DataStoreService = game:GetService("DataStoreService")

local template = {
    Level = 0,
    Coins = 0,
    Inventory = {},
    Version = 0,
}

local function StateChanged(state, dataStore)
    while dataStore.State == false do
        if dataStore:Open(template) ~= "Success" then task.wait(6) end
    end
end

game.Players.PlayerAdded:Connect(function(player)
    local dataStore = DataStoreModule.new("Player", player.UserId)
    dataStore.StateChanged:Connect(StateChanged)
    StateChanged(dataStore.State, dataStore)
end)

game.Players.PlayerRemoving:Connect(function(player)
    local dataStore = DataStoreModule.find("Player", player.UserId)
    if dataStore == nil then return end -- when the server starts to shutdown it might destroy the datastore so a backup might not be saved for the players online while the server shuts down
    dataStore.Value.Version += 1
    dataStore:Destroy()
    local playerBackupStore = DataStoreService:GetDataStore("PlayerBackup", dataStore.Key)
    local success = pcall(playerBackupStore.SetAsync, playerBackupStore, dataStore.Value.Version, dataStore.Value)
end)

here is another version that is a bit more safer

local DataStoreModule = require(11671168253)
local DataStoreService = game:GetService("DataStoreService")

local template = {
    Level = 0,
    Coins = 0,
    Inventory = {},
    Version = 0,
}

local function StateChanged(state, dataStore)
    while dataStore.State == false do
        if dataStore:Open(template) ~= "Success" then task.wait(6) end
    end
end

local function Saving(value, dataStore)
    value.Version += 1
    local playerBackupStore = DataStoreService:GetDataStore("PlayerBackup", dataStore.Key)
    local success = pcall(playerBackupStore.SetAsync, playerBackupStore, value.Version, value)
end

game.Players.PlayerAdded:Connect(function(player)
    local dataStore = DataStoreModule.new("Player", player.UserId)
    dataStore.StateChanged:Connect(StateChanged)
    dataStore.Saving:Connect(Saving)
    StateChanged(dataStore.State, dataStore)
end)

game.Players.PlayerRemoving:Connect(function(player)
    local dataStore = DataStoreModule.find("Player", player.UserId)
    if dataStore ~= nil then dataStore:Destroy() end
end)
2 Likes

Definitely recommending to anyone reading this to use SDM, it is super easy to use and I don’t have to worry much about data stores while I focus on the core aspects of my game!

Any benefits to using it over profileservice (for your specific use-case, i know there’s multiple videos on this)

Is this optimized/made for more than one datastore? I would think so but I just need to be sure