The title says it all, I want help and feedback on what should I improve on my data manager module but also I want feedback about the replication method I’m using in this code.
I have tested this replication method and it works fine, but is it really fine?
The replication method I’m using is simple:
- Every time I call
Increment
orSet
functions I call another function to replicate the new changes that have been made to the player’s data, I think its similar to how__newIndex
works. - I send 3 arguments, the
Player
Object, the name of the data that is being Incremented or set, example: “Cash” and the newest value of “Cash” insessionData[player.UserId]
.
Example:
-- index is equivalent to "Cash" and the value is the newest value on the index "Cash" in sessionData[player.UserId] table.
self:OnUpdate(player, index, value) -- (player, "Cash", sessionData[index])
ModuleScript:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataStoreService = game:GetService("DataStoreService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local await = require(ReplicatedStorage.await) -- a custom wait
local DATA_STORE_NAME = "GLOBAL"
local DATA_STORE_SCOPE = "PlayerData"
local MAX_GET_ATTEMPTS = 6
local MAX_SAVE_ATTEMPTS = 8
local gameId = game.GameId
local jobId = game.JobId
local isStudio = RunService:IsStudio()
local DataStore = nil
if gameId ~= 0 then
DataStore = DataStoreService:GetDataStore(DATA_STORE_NAME, DATA_STORE_SCOPE)
end
local DataListener = ReplicatedStorage.Remotes.DataStoreRequests.DataListener
local sessionData = {}
local defaultData = {
Cash = 0
}
local function loadPlayerData(player)
local id = tostring(player.UserId)
local attempts = 0
local success, result = nil, nil
sessionData[id] = defaultData
while not success and attempts < MAX_GET_ATTEMPTS do
success, result = pcall(DataStore.GetAsync, DataStore, id)
if not success then
attempts = attempts + 1
warn(result)
await(1)
end
end
if success then
if result and (type(result) == "table") then
sessionData[id] = result
end
local BoolValue = Instance.new("BoolValue")
BoolValue.Name = "finished " .. player.Name
BoolValue.Value = true
BoolVlaue.Parent = game.ReplicatedStorage.PlayerData
else
warn(result)
end
end
local function savePlayerData(player)
if isStudio then
return
end
local id = tostring(player.UserId)
local attempts = 0
local success, result = nil, nil
while not success and attempts < MAX_SAVE_ATTEMPTS do
success, result = pcall(DataStore.UpdateAsync, DataStore, id, function(data)
if data == nil then
return sessionData[id]
elseif data ~= nil and data.SessionJobId == nil or data.SessionJobId == jobId then
data.SessionJobId = jobId
if data ~= sessionData[id] then
return sessionData[id]
end
return nil
end
return nil
end)
if not success then
attempts = attempts + 1
warn(result)
await(6)
end
end
if success then
warn(("[DataStore]: %s's data has been saved with %s attempts."):format(player.Name, attempts))
else
warn(result)
end
sessionData[id] = nil
end
for _, player in ipairs(Players:GetPlayers()) do
coroutine.wrap(loadPlayerData)(player)
end
Players.PlayerAdded:Connect(loadPlayerData)
Players.PlayerRemoving:Connect(savePlayerData)
game:BindToClose(function()
if not isStudio then
for _, player in ipairs(Players:GetPlayers()) do
coroutine.wrap(savePlayerData)(player)
end
end
end)
local DataManager = {}
DataManager.__index = DataManager
function DataManager:GetData(player)
assert(player:IsA("Player") == true)
local id = tostring(player.UserId)
if sessionData[id] then
return sessionData[id]
end
return nil
end
function DataManager:Get(player, index)
local data = self:GetData(player)
if data and data[index] then
return data[index]
end
return nil
end
function DataManager:Set(player, index, value)
local data = self:GetData(player)
if data and data[index] and (type(data[index]) == type(value)) then
data[index] = value
end
self:OnUpdate(player, index, data[index]) -- ahem
end
function DataManager:Increment(player, index, value)
assert(type(value) == "number", "Argument 3 must be a number.")
local data = self:GetData(player)
if data and data[index] then
data[index] = data[index] + value
end
self:OnUpdate(player, index, data[index]) -- ahem
end
--[[
function that will handle the data replication for the client.
how it works?;
:OnUpdate(player, index, value) is called every time set & increment functions are called.
it works like a __newIndex metamethod, i guess.
--]]
function DataManager:OnUpdate(player, ...)
DataListener:FireClient(player, ...)
end
return DataManager
Server:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local DataManager = require(game:GetService("ServerStorage").DataManager)
local BindableEvent = Instance.new("BindableEvent")
-- this is how I wait my data to load, using ChildAdded:Wait() and I think its the right way to do it.
-- learn more about it: https://devforum.roblox.com/t/avoiding-wait-and-why/244015
local function onChildAdded(player, child)
if child.Name == "finished " .. player.Name then
BindableEvent:Fire()
child:Destroy()
end
end
Players.PlayerAdded:Connect(function(player)
ReplicatedStorage.PlayerData.ChildAdded:Connect(function(child)
childAdded(player, child)
end)
BindableEvent.Event:Wait()
DataManager:Increment(player, "Cash", 50)
end)
Client:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Remotes = ReplicatedStorage.Remotes
local DataStoreRequests = Remotes.DataStoreRequests
DataStoreRequests.DataListener.OnClientEvent:Connect(function(index, value)
print(index, value) -- Cash, 50
end)