Guide: Actors & Parallel Luau
Running code inside a game's parallel actor states, and passing messages back across the boundary.
Roblox runs Actor instances in their own Luau states so their scripts can execute in parallel. Those states are isolated: a value in one is not visible from another, and your script starts life outside all of them.
That isolation is the point and the problem. Games increasingly put anti-cheat and gameplay logic inside actors, so if you want to see or change it you have to get code *into* the actor's state rather than calling from outside it.
for _, actor in ipairs(getactors()) do
print(actor:GetFullName())
for _, thread in ipairs(getactorthreads(actor)) do
print(" thread", thread)
end
end
print("currently inside an actor?", isparallel())
print("current actor:", get_current_actor())run_on_actor compiles and runs source inside the actor's state. What you pass is source, not a closure — a closure could not cross the boundary, which is exactly the isolation being worked around.
local actor = getactors()[1]
run_on_actor(actor, [[
-- This runs inside the actor's own state.
print("hello from", get_current_actor())
print("parallel:", isparallel())
]])No upvalues cross the boundary
The source string is the entire contract. Anything the code needs must be inside it, or arrive through a channel — locals from the calling script do not exist over there.
create_comm_channel opens a BindableEvent-backed pipe with an id you can pass into the actor as text. The actor picks it up with get_comm_channel and both sides then have a normal signal to talk over.
local id, channel = create_comm_channel()
channel.Event:Connect(function(...)
print("from the actor:", ...)
end)
run_on_actor(getactors()[1], ([[
local channel = get_comm_channel(%d)
channel:Fire("ready", tick())
]]):format(id))on_actor_state_created fires whenever a new actor state appears, *before* it runs any queued code — which is the hook you want if you need to be inside an actor from its first instruction.
on_actor_state_created:Connect(function(actor)
run_on_actor(actor, [[ getgenv().__seen = true ]])
end)The Actors chapter has the full entry list. Environment is worth reading alongside it — getgenv, getrenv and gettenv behave differently depending on which state you ask from.