I’m seeking your expertise to review my script that saves player currency data using DataStore. I have a few concerns:
- API Limits: Could my script potentially hit API limits? If so, what strategies can I implement to avoid this?
- Data Loss: Are there any common pitfalls that could lead to data loss for players?
- General Suggestions: Any tips or best practices for optimizing DataStore usage would be greatly appreciated!
Here’s the relevant portion of my script:
local DataStoreService = game:GetService("DataStoreService")
local CurrencyDataStore = DataStoreService:GetDataStore("PlayerCurrencyData1")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SaveQueue = {}
local IsSaving = {}
local SAVE_INTERVAL = 60
local CURRENCY_1 = "Coins"
local CURRENCY_2 = "Gems"
local DEFAULT_CURRENCY_1 = 100
local DEFAULT_CURRENCY_2 = 0
local SYNC_INTERVAL = 5
local RemoteFolder = ReplicatedStorage:FindFirstChild("RemoteEvents") or Instance.new("Folder")
RemoteFolder.Name = "RemoteEvents"
RemoteFolder.Parent = ReplicatedStorage
local CurrencyUpdateEvent = RemoteFolder:FindFirstChild("CurrencyUpdate") or Instance.new("RemoteEvent")
CurrencyUpdateEvent.Name = "CurrencyUpdate"
CurrencyUpdateEvent.Parent = RemoteFolder
local function formatNumber(number)
if not number or type(number) ~= "number" then
return "0"
end
if number >= 1000000 then
local formatted = number / 1000000
if formatted == math.floor(formatted) then
return math.floor(formatted) .. "M"
else
return string.format("%.1fM", formatted):gsub("%.0+M$", "M")
end
elseif number >= 1000 then
local formatted = number / 1000
if formatted == math.floor(formatted) then
return math.floor(formatted) .. "K"
else
return string.format("%.1fK", formatted):gsub("%.0+K$", "K")
end
else
return tostring(math.floor(number))
end
end
local function loadPlayerData(player)
local playerKey = "ID_" .. player.UserId
local success, data = pcall(function()
return CurrencyDataStore:GetAsync(playerKey)
end)
if success and data then
return data
else
return {[CURRENCY_1] = DEFAULT_CURRENCY_1, [CURRENCY_2] = DEFAULT_CURRENCY_2}
end
end
local function savePlayerData(player, immediate)
local playerKey = "ID_" .. player.UserId
local playerUserId = player.UserId
local data = {
[CURRENCY_1] = player:GetAttribute(CURRENCY_1) or DEFAULT_CURRENCY_1,
[CURRENCY_2] = player:GetAttribute(CURRENCY_2) or DEFAULT_CURRENCY_2
}
if immediate then
if IsSaving[playerUserId] then
warn("Already saving currency data for player: " .. player.Name)
return
end
IsSaving[playerUserId] = true
local success, err = pcall(function()
CurrencyDataStore:SetAsync(playerKey, data)
end)
IsSaving[playerUserId] = nil
if not success then
warn("Failed to save player data for " .. player.Name .. ": " .. err)
else
print("Immediately saved currency data for player:", player.Name)
end
else
SaveQueue[playerUserId] = {
Key = playerKey,
Data = data,
LastUpdate = os.time()
}
print("Queued currency save for player:", player.Name)
end
end
task.spawn(function()
while true do
task.wait(SAVE_INTERVAL)
local playersToSave = {}
for userId, saveData in pairs(SaveQueue) do
table.insert(playersToSave, {userId = userId, saveData = saveData})
SaveQueue[userId] = nil
end
for _, data in ipairs(playersToSave) do
local userId = data.userId
local saveData = data.saveData
if not IsSaving[userId] then
IsSaving[userId] = true
local success, errorMessage = pcall(function()
CurrencyDataStore:SetAsync(saveData.Key, saveData.Data)
end)
IsSaving[userId] = nil
if not success then
warn("Error while batch saving currency data: " .. errorMessage)
SaveQueue[userId] = saveData
else
print("Batch saved currency data for player ID:", userId)
end
task.wait(0.2)
else
SaveQueue[userId] = saveData
end
end
end
end)
local function updatePlayerCurrency(player, currency, amount)
local oldValue = player:GetAttribute(currency) or 0
player:SetAttribute(currency, amount)
print(player.Name .. "'s " .. currency .. " updated from " .. oldValue .. " to " .. amount)
CurrencyUpdateEvent:FireClient(player, currency, amount, formatNumber(amount))
end
local function syncPlayerData(player)
local data = loadPlayerData(player)
local currentCoins = player:GetAttribute(CURRENCY_1)
local currentGems = player:GetAttribute(CURRENCY_2)
if data[CURRENCY_1] ~= currentCoins then
updatePlayerCurrency(player, CURRENCY_1, data[CURRENCY_1])
end
if data[CURRENCY_2] ~= currentGems then
updatePlayerCurrency(player, CURRENCY_2, data[CURRENCY_2])
end
end
local function forceSyncPlayer(player)
task.spawn(function()
syncPlayerData(player)
end)
end
game.Players.PlayerAdded:Connect(function(player)
local data = loadPlayerData(player)
player:SetAttribute(CURRENCY_1, data[CURRENCY_1])
player:SetAttribute(CURRENCY_2, data[CURRENCY_2])
CurrencyUpdateEvent:FireClient(player, CURRENCY_1, data[CURRENCY_1], formatNumber(data[CURRENCY_1]))
CurrencyUpdateEvent:FireClient(player, CURRENCY_2, data[CURRENCY_2], formatNumber(data[CURRENCY_2]))
task.spawn(function()
while player and player.Parent do
task.wait(SYNC_INTERVAL)
syncPlayerData(player)
end
end)
end)
game.Players.PlayerRemoving:Connect(function(player)
savePlayerData(player, true)
end)
game:BindToClose(function()
for _, player in pairs(game.Players:GetPlayers()) do
savePlayerData(player, true)
end
end)
local CurrencySystem = {}
function CurrencySystem.AddCurrency(player, currency, amount, immediate)
if currency ~= CURRENCY_1 and currency ~= CURRENCY_2 then
warn("Invalid currency type: " .. currency)
return false
end
local currentAmount = player:GetAttribute(currency) or 0
local newAmount = currentAmount + amount
updatePlayerCurrency(player, currency, newAmount)
task.spawn(function()
savePlayerData(player, immediate)
end)
return true
end
function CurrencySystem.SetCurrency(player, currency, amount, immediate)
if currency ~= CURRENCY_1 and currency ~= CURRENCY_2 then
warn("Invalid currency type: " .. currency)
return false
end
updatePlayerCurrency(player, currency, amount)
task.spawn(function()
savePlayerData(player, immediate)
end)
return true
end
function CurrencySystem.GetCurrency(player, currency)
if currency ~= CURRENCY_1 and currency ~= CURRENCY_2 then
warn("Invalid currency type: " .. currency)
return 0
end
return player:GetAttribute(currency) or 0
end
function CurrencySystem.GetFormattedCurrency(player, currency)
local value = CurrencySystem.GetCurrency(player, currency)
return formatNumber(value)
end
function CurrencySystem.SyncAllPlayers()
for _, player in pairs(game.Players:GetPlayers()) do
task.spawn(function()
syncPlayerData(player)
end)
end
end
function CurrencySystem.SyncPlayer(player)
if player and player.Parent then
forceSyncPlayer(player)
return true
end
return false
end
function CurrencySystem.FormatNumber(number)
return formatNumber(number)
end
return CurrencySystem```