Davi Datastore FRAMEWORK

Welcome to the Davi DataStore module for Roblox Studio! This Lua-based module provides a robust and customizable solution for efficiently storing and retrieving player data in your Roblox games. With a focus on flexibility and ease of use, the DataStore module allows you to seamlessly integrate persistent data management into your game mechanics.
Modulescript:

DataStore System Documentation

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")

local DefaultStoreName = "PlayerDataStore"
local Stores = {}
local Cache = {}
local DirtyData = {}
local SaveInterval = 300 
local AutoSaveEnabled = true

local DataStore = {}
DataStore.DataLoaded = Instance.new("BindableEvent")
DataStore.DataSaved = Instance.new("BindableEvent")
DataStore.DataChanged = Instance.new("BindableEvent")

local Permissions = {
	Admins = {},
	Moderators = {},
}

local GameSettings = {
	EncryptionKey = "kkkk", -- you can change this to make your encryptionkey
	UseTransactions = true,
	UseVersionControl = true,
}

function DataStore:GetStore(player, storeName, gameName)
	storeName = storeName or DefaultStoreName
	gameName = gameName or game.PlaceId
	local key = storeName .. "_" .. gameName .. "_" .. player.UserId

	if not Stores[key] then
		Stores[key] = DataStoreService:GetDataStore(storeName .. "_" .. gameName)
	end

	return Stores[key]
end

function DataStore:SaveData(player, data, async, checkDirty)
	local playerKey = tostring(player.UserId)
	local storeName = player.DataStoreName or DefaultStoreName
	local gameName = player.GameName or game.PlaceId

	local success, error = pcall(function()
		local dataStore = self:GetStore(player, storeName, gameName)

		if checkDirty and not self:HasDataChanged(player) then
			return
		end

		if GameSettings.UseVersionControl then
			data.Version = data.Version and data.Version + 1 or 1
		end

		local serializedData = HttpService:JSONEncode(data)

		if GameSettings.UseTransactions then
			dataStore:UpdateAsync(playerKey, function(oldData)
				return serializedData
			end)
		else
			if async then
				dataStore:SetAsync(playerKey, serializedData)
			else
				player:SetAttribute("PlayerData", data)
			end
		end

		self.DataSaved:Fire(player)
		self:ClearDirtyData(player)

		if Cache[player] then
			Cache[player] = data
		end

		self:LogDataChange(player, data)
	end)

	if not success then
		warn("Failed to save data for player " .. player.Name .. ": " .. error)
	end
end

function DataStore:LoadData(player, async)
	local playerKey = tostring(player.UserId)
	local data = nil
	local storeName = player.DataStoreName or DefaultStoreName
	local gameName = player.GameName or game.PlaceId

	local success, error = pcall(function()
		local dataStore = self:GetStore(player, storeName, gameName)

		if async then
			local result = dataStore:GetAsync(playerKey)
			data = result and HttpService:JSONDecode(result) or nil
		else
			data = player:GetAttribute("PlayerData")
		end

		if Cache[player] then
			Cache[player] = data
		end

		self.DataLoaded:Fire(player)
	end)

	if not success then
		warn("Failed to load data for player " .. player.Name .. ": " .. error)
		self:HandleLoadDataFailure(player)
	end

	return data
end

function DataStore:SetStoreName(player, storeName)
	player.DataStoreName = storeName
end

function DataStore:SetGameName(player, gameName)
	player.GameName = gameName
end

function DataStore:ResetData(player)
	local playerKey = tostring(player.UserId)
	local storeName = player.DataStoreName or DefaultStoreName
	local gameName = player.GameName or game.PlaceId

	local success, error = pcall(function()
		local dataStore = self:GetStore(player, storeName, gameName)
		dataStore:RemoveAsync(playerKey)
	end)

	if not success then
		warn("Failed to reset data for player " .. player.Name .. ": " .. error)
	end
end

function DataStore:LoadDataWithRetry(player, maxRetries, retryDelay)
	local retries = 0
	repeat
		self:LoadData(player, true)
		wait(retryDelay)
		retries = retries + 1
	until retries >= maxRetries or player:GetAttribute("PlayerData") ~= nil
end

function DataStore:HandleLoadDataFailure(player)
	warn("Handling load data failure for player " .. player.Name)
end

function DataStore:BatchSaveData(players, data, async, checkDirty)
	for _, player in pairs(players) do
		self:SaveData(player, data, async, checkDirty)
	end
end

function DataStore:BatchLoadData(players, async)
	local loadedData = {}
	for _, player in pairs(players) do
		loadedData[player] = self:LoadData(player, async)
	end
	return loadedData
end

function DataStore:SetDirtyData(player)
	DirtyData[player] = true
	self.DataChanged:Fire(player)
end

function DataStore:HasDataChanged(player)
	return DirtyData[player] == true
end

function DataStore:ClearDirtyData(player)
	DirtyData[player] = nil
end

function DataStore:LogDataChange(player, data)
	print("Logging data change for player " .. player.Name .. ": " .. HttpService:JSONEncode(data))
end

function DataStore:ExportData(player)
	local data = player:GetAttribute("PlayerData")
	return HttpService:JSONEncode(data)
end

function DataStore:ImportData(player, exportedData)
	local data = HttpService:JSONDecode(exportedData)
	player:SetAttribute("PlayerData", data)
	self:SetDirtyData(player)
end


function DataStore:AddAdmin(player)
	table.insert(Permissions.Admins, player.UserId)
end

function DataStore:AddModerator(player)
	table.insert(Permissions.Moderators, player.UserId)
end

function DataStore:IsAdmin(player)
	return table.find(Permissions.Admins, player.UserId) ~= nil
end

function DataStore:IsModerator(player)
	return table.find(Permissions.Moderators, player.UserId) ~= nil
end

function DataStore:AutoSaveLoop()
	while AutoSaveEnabled do
		wait(SaveInterval)
		for player, _ in pairs(DirtyData) do
			self:SaveData(player, player:GetAttribute("PlayerData"), true, true)
		end
	end
end

spawn(DataStore.AutoSaveLoop)

return DataStore

Overview

This Lua-based DataStore system for Roblox Studio provides an efficient and flexible solution for storing and retrieving player data. The system is designed to be customizable, allowing you to adjust settings according to the specific needs of your game.

Main Components

1. DataStore

The DataStore object is the main interface for interacting with the system. It provides methods for saving, loading, and manipulating player data. Additionally, events are triggered to notify about important events, such as data loading or saving.

  • DataStore.DataLoaded: Event triggered when player data is loaded.
  • DataStore.DataSaved: Event triggered when player data is saved.
  • DataStore.DataChanged: Event triggered when player data is locally modified.

2. Settings

The system includes a set of adjustable settings that can be modified to suit your gameā€™s specific needs.

  • DefaultStoreName: Default name of the DataStore.
  • SaveInterval: Interval between automatic saves.
  • AutoSaveEnabled: Enables or disables automatic saving.
  • GameSettings.EncryptionKey: Key for encrypting stored data.
  • GameSettings.UseTransactions: Uses transactions when saving data.
  • GameSettings.UseVersionControl: Controls data versioning.

3. Permissions

The system includes a simple permissions system that allows designating administrators and moderators.

  • Permissions.Admins: List of administrator user IDs.
  • Permissions.Moderators: List of moderator user IDs.

Basic Usage

Initial Setup

  1. Include the code in a module in Roblox Studio.
  2. Ensure that DataStoreService, Players, and HttpService are available in the environment.
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")

-- DataStore code here

Create DataStore Instance

local DataStore = require(PATH_TO_MODULE)  -- Replace with the correct path

local player = game.Players.LocalPlayer
local myDataStore = DataStore:GetStore(player)

Save and Load Data

-- Save data
local dataToSave = {score = 100, level = 5}
DataStore:SaveData(player, dataToSave, false, true)  -- Saves synchronously and checks local changes

-- Load data
local loadedData = DataStore:LoadData(player, true)  -- Loads asynchronously
print(loadedData.score)  -- Access loaded data

Settings and Permissions

-- Set DataStore name for a specific player
DataStore:SetStoreName(player, "CustomDataStore")

-- Add an administrator
DataStore:AddAdmin(player)

-- Check permissions
if DataStore:IsAdmin(player) then
    print(player.Name .. " is an administrator.")
}

Export and Import Data

-- Export data
local exportedData = DataStore:ExportData(player)

-- Import data
DataStore:ImportData(player, exportedData)

Automatic Saving

Automatic saving is enabled by default but can be disabled if necessary.

-- Disable automatic saving
DataStore.AutoSaveEnabled = false

Advanced Features

The system also offers advanced features such as version control, encryption, and transactions. Refer to the internal documentation in the code for details on these features and how to use them.

7 Likes

So which Datastore do we compare yours to see which is better?

ProfileService?
Datastore2?

i donā€™t mean in a bad way but to see which one is better to use.

2 Likes

bro still using wait & spawn without task library o.0

ā€œDocumentationā€ what??

and no types here :confused:

2 Likes

Probably meant ā€œModule Scriptā€ i guess.

i gonna use task.spawn and task.wait in the next update

Itā€™s funny to see how people make datastore modules when profileservice and DS2 exists

so likeā€¦ what do datastore admins/moderators doā€¦?

Preferences may or may not be a thingšŸ˜²

Why would you prefer a datastore module that has less features than other popular datastore modules like SDM?

Personally I donā€™t, but assumably if I had to guess for others

  • Not as complicated
  • If they are the developer they fully understand the code

The list could go on, Iā€™m just saying why undervalue what people do just because thereā€™s ones more performant/better

I mean like, if they make a better one it will definetly use it, but the ones with only saving and limited functionsā€¦ i see no use on them since there is already DataStore2

I will never use a Datastore Module. Roblox Datastore is good enough; you just have to handle join, leave, shutdown, and autosave. Also, I have a simple way to load and store data by creating a ā€˜Dataā€™ folder inside the player, making the data from every script easily accessible without the need to call any modules. Itā€™s also easy to get changed values by just using GetPropertyChangedSignal.

Here is a basic example of how I do it:

plrAdded function:

local success, plrData = pcall(function()
    return PlayerData:GetAsync(plr.UserId)
end)
plrData = success and plrData or defaultData
loadPlrData(plr, plrData)

loadPlrData function:

local function loadPlrData(plr, data, parent)
    local dataF = parent or (plr:FindFirstChild("Data") or Instance.new("Folder", plr))
    if not parent then
        dataF.Name = "Data"
    end

    for key, value in pairs(data) do
        local typeOf = type(value)
        local typeOfValue = typeOfLookup[typeOf]

        if typeOfValue then
            local valueF = dataF:FindFirstChild(key) or Instance.new(typeOfValue, dataF)
            valueF.Name = key
            valueF.Value = value
        elseif typeOf == "table" then
            local tableF = dataF:FindFirstChild(key) or Instance.new("Folder", dataF)
            tableF.Name = key
            loadPlrData(plr, value, tableF)
        end
    end
end

getPlrData function:

local function getPlrData(plr, parent)
    local dataF = parent or plr:FindFirstChild("Data")
    if not dataF then
        return
    end

    local data = {}

    for _, value in pairs(dataF:GetChildren()) do
        local typeOf = value.ClassName
        local key = value.Name
        if typeOf == "StringValue" or typeOf == "IntValue" or typeOf == "BoolValue" then
            data[key] = value.Value
        elseif typeOf == "Folder" then
            data[key] = getPlrData(plr, value)
        end
    end

    return data
end

typeOfLookup Table:

local typeOfLookup = {
	["string"] = "StringValue",
	["number"] = "IntValue",
	["boolean"] = "BoolValue",
}

Result:

image

1 Like