Add an ability to see if a thread is part of an actor, and if so to see if it's running in a synchronized or desynchronized mode

Problem

As a Roblox developer, it is impossible to determine if a thread is parented to an actor, and if so, if it’s synchronized or desynchronized.

Background

My games utilize a series of self-contained modules called packages (not the Roblox definition, but the Unix/Windows definition) which are individual actors. Everything that relates to the package and it’s functionality is contained in the package. Assets such as remote events, GUIs, sounds, animations, etc… are part of the package and are either copied to or created in their respective destinations when the package initializes itself. One of the items that’s copied is an include file (treated similar to a header file in C/C++) that is placed in the includes folder which contains the public methods of the package. Although many methods utilize Actor:SendMessage() to call public methods in the actor, some methods return data which cannot be retrieved though this call. Therefore a bindable function is used instead. Bindable functions block the calling thread until the function returns which makes them ideal for requesting data from an actor. Although the utilization of shared tables is possible, there’s no real way of knowing when the data returns due to the asynchronous nature of using Actor:SendMessage() and looking for the result in a shared table.

Use Cases

This issue stems from the fact that communication between actors in which data is returned are using the aforementioned bindable functions which can only be called in a serial context. Each actor can have a module script that is treated as an include file which called the bindable function. This allows other actors to simply include the file and make a normal function call with the underlying mechanics hidden in the include file. However, since bindable functions must be called in a serial context, it’s important for the wrapping function to know if the caller is part of an actor, and if so, if it’s in a serial or parallel context so it can switch modes if needed, and then switch back when the function returns, if needed. As it currently stands, the code currently looks like this:

Include.LUA

-- Scripting Support
local packageName = "SomePackage"
local childWaitTime = 10
local serverScriptService = game:GetService("ServerScriptService")
local actor = serverScriptService:WaitForChild("SomePackage", childWaitTime)
assert(actor, packageName .. " actor failed to load.")
local coreLibs = serverScriptService:WaitForChild("Libraries", childWaitTime)
assert(coreLibs, "Core library script load failure.")



-- ******** Local Data

local public = actor:WaitForChild("Assets")



-- ******** Functions/Methods

local function someFunction(param1, param2, param3)
	return public.BindableFunction:Invoke(param1, param2, param3)
end

To call the above function, a caller running in parallel mode would need to use the following code (modInclude contains the result of the require statement):

task.synchronize()
local result = modInclude.someFunction(param1, param2, param3)
task.desynchronize()

The above code is needed every time a call to an actor is required when the caller is operating in a parallel context and the underlying mechanism is a bindable function.

Proposed Solutions

A proposed solution would be to add an method to the task library or the coroutine library. Such a method, when called, could return an Enum.ThreadActorContext value. The individual values could be the following:

Name Value Definition
NotActor 0 The calling thread is not part of an actor.
Synchronized 1 The calling thread is part of an actor and is running in a synchronized context.
Desynchronized 2 The calling thread is part of an actor and is running in a desynchronized context.
Unknown 3 The context of the calling thread is unknown or cannot be determined.

The usage of such a solution within the include file would look something like this with the new method named ActorThreadStatus:

local function someFunction(param1, param2, param3)
	local state = task:ActorThreadStatus()
	if state == Enum.ThreadActorContext.Desynchronized then
		task:synchronize()
	end
	local result = public.BindableFunction:Invoke(param1, param2, param3)
	if state == Enum.ThreadActorContext.Desynchronized then
		task:desynchronize()
	end
	return result
end

In the spirit of the DRY principle, much of the above code could be place in two functions so the LUA would look something like this:

local function setSyncState()
	local state = task:ActorThreadStatus()
	if state == Enum.ThreadActorContext.Desynchronized then
		task:synchronize()
	end
	return state
end

local function setDesyncState(state)
	if state == Enum.ThreadActorContext.Desynchronized then
		task:desynchronize()
	end
end

local function someFunction(param1, param2, param3)
	local state = setSyncState
	local result = public.BindableFunction:Invoke(param1, param2, param3)
	setDesyncState(state)
	return result
end

An alternative solution would be to have the above check code integrated into the BindableFunction:Invoke() method directly, which would be faster to process and be more seamless to the developer.

Conclusion

If Roblox is able to address this issue, it would improve my development experience by allowing the library/include functions to determine for themselves what the caller execution context is and then switching back to that when done instead of placing the burden on the caller which will increase code complexity.

6 Likes

Thank you for the suggestion.

We are looking into the possibility of allowing BindableFunction to work in parallel context.
It seems that will result in better developer experience than having to check current execution state manually, based on the use case you provided.

3 Likes

That works even better for bindable functions.

Thank you for your response.

fair enough this feature would be cool

I found a way to recreate this feature without waiting 999 years for roblox to do something :skull:

function ActorThreadStatus()
	return pcall(function()
		Instance.new("Part"):Destroy()
	end)==true and "Synchronized" or "Desynchronized"
end
task.desynchronize()
print(ActorThreadStatus()) --Desynchronized
task.synchronize()
print(ActorThreadStatus()) --Synchronized

You cant use “Destroy” when thread is desynchronized so that pcall is gonna return false which means thread is desynchronized

if its synchronized that pcall is gonna return true

This is still not the best I think if roblox added something better than that internally then it would be better than mine obviously :skull:

Hmm… Interesting. The one thing that this is missing is the indication if the thread is part of an actor or not. That detection would have to be foolproof because of module scripts that get included that is not a descendant of an actor.

However, if Roblox was to allow bindable functions to be called in parallel, that would be great too.

Is there any update on this? I’ve ran into a situation where I really need to know if a thread is in serial or parallel execution, and not just being able to call bindable functions in parallel.

EDIT: Based on @Bakonowychlopak123’s proposed solution, I’ve developed these functions to deal with the issue.


local parallelPart = Instance.new("Part")


-- Checks if the calling thread is in series or parallel
-- execution state.
local function checkThreadContext()
	local parallel = false
	local status, result = pcall(function()
		parallelPart.Anchored = true
	end)
	if status == true then
		status, result = pcall(function()
			task.synchronize()
		end)
		if status == true then
			parallel = false
		else
			parallel = nil
		end
	else
		parallel = true
	end
	return parallel
end

-- Sets the thread context to serial if needed.
local function setThreadSerial()
	local status = checkThreadContext()
	if status == true then
		task.synchronize()
	end
	return status
end

-- Sets the thread context to parallel if needed.
local function setThreadParallel(status)
	if status == true then
		task.desynchronize()
	end
end

This would be better if Roblox actually made this functionality internal to the engine, but I don’t see that happening any time soon.