World Executor · Roblox VM

Guide: Actors & Parallel Luau

Running code inside a game's parallel actor states, and passing messages back across the boundary.

Why actors matter

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.

Finding them

Enumerating actors
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())

Running code inside one

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.

run_on_actor
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.

Talking back

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.

A round trip
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.

Getting in early
on_actor_state_created:Connect(function(actor)
    run_on_actor(actor, [[ getgenv().__seen = true ]])
end)

Related reference

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.