How to communicate between VMs in parallel

I can’t just require a module and start calling functions in it, even if my LocalScript isn’t in parallel because the module script I want to communicate with has global state and everything breaks once my LocalScript is in an actor before I’ve even called task.Desyncronize().

When I try to use BindableFunctions I get an error for trying to call a function from another VM.
BindableEvents don’t seem to work across VMs though I haven’t done much testing.

How on earth am I supposed to communicate between scripts on different VMs?

2 Likes

Passing closures across VMs would be a blatant violation of thread-safety (What if you passed a function that had an upvalue, and it was deemed constant in one VM when another suddenly mutates to it? Which VM’s garbage collector should collect it?)

You ultimately have two options:

  1. Use SharedTables to sync and update data between VMs
  2. Use bindables to pass by value between VMs

Ironically, Roblox’s SharedTable implementation is extraordinarily slower in comparison to using bindable events, which I have found to still be alarmingly sluggish in comparison to just iterating through a serial thread.

4 Likes

I see. Which bindables are you using? As I mentioned before, I tried using a bindable function and it just threw an error.

Do you think it’s even worth it for me to use an actor? I’m pretty much just making a workspace:GetPartBoundsInRadius call and iterating over the results every frame with some caching to reduce the performance hit. I’m only using an actor so this one module will hopefully run faster.

The issues with communicating across VMs comes from the script that uses this module trying to communicate with other scripts.

local module = {}

if not script.Parent then print(script:GetFullName(), "has no parent") return end
local Character = script:FindFirstAncestorOfClass("Model")
local Humanoid : Humanoid = Character:WaitForChild("Humanoid")
local Root : Part = Character:WaitForChild("HumanoidRootPart")

local LineOfSightParams = RaycastParams.new()
LineOfSightParams.FilterType = Enum.RaycastFilterType.Include

local RadiusCheckParams = OverlapParams.new()
RadiusCheckParams.FilterType = Enum.RaycastFilterType.Include

local NearbyTilesExpiration = 2
local _NearbyTiles = {}
local _NearbyTiles_LastTick = -100

module.Discriminators = {
	ClosestPaintedTile = function(tile, point)
		return Humanoid.TeamColor.Value == tile.Color
	end;
	ClosestUnpaintedTile = function(tile, point)
		return Humanoid.TeamColor.Value ~= tile.Color
	end;
	ClosestUnpaintedTileLOS = function(tile : BasePart, point)
		local tilePos = tile.Position + Vector3.yAxis * 2.5
		
		local LOShitInfo = workspace:Raycast(point, tilePos - point, LineOfSightParams)
		return Humanoid.TeamColor.Value ~= tile.Color and (not LOShitInfo or LOShitInfo.Instance ~= tile)
	end;
}

function module:GetTileClosestToDistance(point : Vector3?, radius : number, discriminatorName : string, desiredDistance : number?)
	local discriminator = module.Discriminators[discriminatorName]
	if not discriminator then error(tostring(discriminatorName).. " is not a valid discriminator function..",2) end
	
	RadiusCheckParams.FilterDescendantsInstances = {workspace.Map.Ground}
	LineOfSightParams.FilterDescendantsInstances = {workspace.Map}
	
	task.desynchronize()
	if not desiredDistance then desiredDistance = 0 end
	
	-- cache results for a while.
	local groundTiles
	if not point then
		point = Root.Position + (Root.AssemblyLinearVelocity * 1)
		if not _NearbyTiles or tick() - _NearbyTiles_LastTick > NearbyTilesExpiration then
			_NearbyTiles_LastTick = tick()
			_NearbyTiles = workspace:GetPartBoundsInRadius(point, radius, RadiusCheckParams)
		end
		groundTiles = _NearbyTiles
	else
		groundTiles = workspace:GetPartBoundsInRadius(point, radius, RadiusCheckParams)
	end

	local closestTile = nil
	local closestDelta = math.huge
	for i, tile : BasePart in ipairs(groundTiles) do
		if not discriminator(tile, point) then continue end

		local distance = (point - tile.Position).Magnitude
		local delta = distance - desiredDistance
		if math.abs(delta) <= closestDelta then
			closestTile = tile
			closestDelta = math.abs(delta)
		end
	end
	
	task.synchronize()
	return closestTile
end

return module

you can use MessagingService to enable cross-server messaging between different servers or client instances of your experience. This allows you to communicate between scripts on different VMs.
Cross-Server Messaging | Documentation - Roblox Creator Hub

When Roblox developers talk about Lua VMs running in parallel, they are almost always going to be talking about Parallel Luau, not how to communicate between Roblox servers. If I needed help communicating between Roblox servers, I would ask for that.

I appreciate the response, but you’ve misunderstood my question. You should really read a users post before responding.

sorry ur right, welp it’s possible that this is not currently supported

You can use Actor:SendMessage and Actor:BindToMessage/Actor:BindToMessageParallel to communicate between Actors running in parallel.

2 Likes

Can this be used to communicate between code in an actor and code not in any actor?

Actor:BindToMessage/Actor:BindToMessageParallel must be called from a descendant script of the Actor.
Actor:SendMessage can be called from anywhere.

1 Like

Hate to necro-post, but what is the whole point of having BindToMessageParallel if both can only be used by a script under an Actor?

1 Like

It’s honestly ridiculous, I spent a day trying to figure out how to send messages between actors and non actors and I ended up just having to use bindable events, which are still affecting my performance because it in serial… Best thing I can do is minimize the amount of data I send through the bindables, my mob AI would allow a lot more mobs to be active if they ran in parallel. I will try using shared tables to send information but it probably will be the same.

Update: Shared Tables worked.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.