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.