Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
COMMUNITY QUESTION

How should I make invoking a BindableFunction yield until a specific moment?

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

0

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

1 answer

Use a BindableEvent, ex DataReady -- Fire it when you have the data, listen to it from where-ever you need it (after checking to make sure that it isn't already loaded).

This discussion is preserved as it was. New answers, comments, and votes are closed.