So I'm working with data and I decided that it could be better to use BindableFunctions instead of using a Module. Well early on I ran into a problem where if I wanted to receive a player's data immediately when after they joined but they hadn't been initialized yet by the data script. So I need to make the call yield until the data is initialized but I also need the yielding stop when too much time has passed.
Here's my data script:
local serverStorage = game:GetService('ServerStorage')
local DSS = game:GetService('DataStoreService')
local statistics = DSS:GetDataStore('general','statistics')
local getData = Instance.new('BindableFunction')
getData.Name = 'GetData'
getData.Parent = serverStorage
local session = {}
local DEFAULTSESSION = {visits = 0}
local MAXIMUMRETRIES = 3
function retry(func)
local success
local retries = 0
local value = nil
repeat
success = pcall(function()
value = func()
end)
retries = retries + 1
if not success then wait(2) end
until success or retries >= MAXIMUMRETRIES
if not success then
warn('Could not access data.')
end
return success,value
end
function get(user) --gets the data for user in form of a session
assert(user and typeof(user) == 'Instance' and user:IsA('Player'),
'Invalid first argument given for "get".')
local key = 'user-'..user.UserId
local success,value = pcall(function()
return retry(function() statistics:GetAsync(key) end)
end)
return success,value
end
function set(user,newValue)
assert(user and typeof(user) == 'Instance' and user:IsA('Player'),
'Invalid first argument given for "set".')
assert(newValue and typeof(newValue) == 'table',
'Invalid second argument given for "set".')
local key = 'user-'..user.UserId
local success = pcall(function()
return retry(function()
statistics:UpdateAsync(key,function(old)
local new = newValue or old or DEFAULTSESSION
return new
end)
end)
end)
return success
end
function saveSession(user)
assert(user and typeof(user) == 'Instance' and user:IsA('Player'),
'Invalid first argument given for "saveSession".')
set(user,session[user])
end
function fixSession(user)
assert(user and typeof(user) == 'Instance' and user:IsA('Player'),
'Invalid first argument given for "fixSession".')
local thisSession = session[user]
if thisSession then
for userKey,userValue in pairs(thisSession) do
if not DEFAULTSESSION[userKey] then
thisSession[userKey] = nil
end
end
for defaultKey,defaultValue in pairs(DEFAULTSESSION) do
if not thisSession[defaultKey] then
thisSession[defaultKey] = defaultValue
end
end
end
end
function getData.OnInvoke(user,request)
assert(user and typeof(user) == 'Instance' and user:IsA('Player'),
'Invalid first argument given for "getData.OnInvoke".')
--yield here
local thisSession = session[user]
if thisSession then
local thisRequest = thisSession[request]
if thisRequest then
return thisRequest
end
end
return false
end
--INITIALIZE WHEN PLAYER JOINS--
function initialize(user)
local success,value = get(user)
if success then
if value then
local fixed = fixSession(value)
session[user] = fixed
else
session[user] = DEFAULTSESSION
end
saveSession(user)
else
--warn('Could not access data.')
end
end
game.Players.PlayerAdded:connect(initialize)
This is the testing/debugging script:
local serverStorage = game:GetService('ServerStorage')
local replicatedStorage = game:GetService('ReplicatedStorage')
local getData = serverStorage:WaitForChild('GetData')
local setData = serverStorage:WaitForChild('SetData')
local incrementData = serverStorage:WaitForChild('IncrementData')
local display = Instance.new('RemoteEvent')
display.Name = 'Display'
display.Parent = replicatedStorage
game.Players.PlayerAdded:connect(function(user)
local key = 'visits'
local value = getData:Invoke(user,key)
print(key..': '..value)
end)
Well, I do have an idea to fix this, but I don't know how well it will work. I could just add a dictionary for initialization which holds the status of initialization for each player. Then I could wait until this value is true. Apparently, you can't make your own signals anymore(except with BindableEvents, but that'd be quite a hassle). The only thing is that I don't know how laggy will this turn out.
Here's an example:
--parts of the code are left out to make this shorter but they are in the above data script
local MAXINITIALIZATIONTIME = 12 --in seconds
local initialization = {}
function getData.OnInvoke(user,request)
assert(user and typeof(user) == 'Instance' and user:IsA('Player'),
'Invalid first argument given for "getData.OnInvoke".')
local time = 0
local lastTick = tick()
repeat wait()
local now = tick()
time = time + (now-lastTick)
--I didn't test this so there could be problems but you get the idea
lastTick = now
until initialization[user] or time > MAXINITIALIZATIONTIME
local thisSession = session[user]
if thisSession then
local thisRequest = thisSession[request]
if thisRequest then
return thisRequest
end
end
return false
end
--INITIALIZE WHEN PLAYER JOINS--
function initialize(user)
initialization[user] = false
local success,value = get(user)
if success then
if value then
local fixed = fixSession(value)
session[user] = fixed
else
session[user] = DEFAULTSESSION
end
saveSession(user)
initialization[user] = true
else
--warn('Could not access data.')
end
end
game.Players.PlayerAdded:connect(initialize)
Please comment on how good would my way of fixing it work, if there's a better way, please post it and feel free to criticize my code.
Thanks,
SwaggyDuckie.
EDIT: I could've just used session[user] to yield but done what is done.

1 comment
I don't know why you'd use BindableFunction when you had a ModuleScript -- ModuleScripts are much more flexible/powerful, imo.