Why does this code not run without task.defer()?

Hello. I am trying to learn how parallel lua works. So, I’ve been looking at some code at the devhub, and I’ve pretty much learned how everything works, but what I don’t understand, is why the code won’t work if I remove a task.defer(function() --[[code]] end) that is on line 20.
Here is the code:

-- Parallel execution requires the use of actors
-- This script clones itself; the original initiates the process, while the clones act as workers

local actor = script:GetActor()
if actor == nil then
	local workers = {}
	for i = 1, 32 do
		local actor = Instance.new("Actor")
		script:Clone().Parent = actor
		table.insert(workers, actor)
	end

	-- Parent all actors under self
	for _, actor in workers do
		actor.Parent = script
	end

	-- Instruct the actors to generate terrain by sending messages
	-- In this example, actors are chosen randomly
	task.defer(function()
		local rand = Random.new()
		local seed = rand:NextNumber()

		local sz = 10
		for x = -sz, sz do
			for y = -sz, sz do
			for z = -sz, sz do
					workers[rand:NextInteger(1, #workers)]:SendMessage("GenerateChunk", x, y, z, seed)
				end
			end
		end
	end)

	-- Exit from the original script; the rest of the code runs in each actor
	return
end

function makeNdArray(numDim, size, elemValue)
	if numDim == 0 then
		return elemValue
	end
	local result = {}
	for i = 1, size do
		result[i] = makeNdArray(numDim - 1, size, elemValue)
	end
	return result
end

function generateVoxelsWithSeed(xd, yd, zd, seed)
	local matEnums = {Enum.Material.CrackedLava, Enum.Material.Basalt, Enum.Material.Asphalt}
	local materials = makeNdArray(3, 4, Enum.Material.CrackedLava)
	local occupancy = makeNdArray(3, 4, 1)

	local rand = Random.new()

	for x = 0, 3 do
		for y = 0, 3 do
			for z = 0, 3 do
				occupancy[x + 1][y + 1][z + 1] = math.noise(xd + 0.25 * x, yd + 0.25 * y, zd + 0.25 * z)
				materials[x + 1][y + 1][z + 1] = matEnums[rand:NextInteger(1, #matEnums)]
			end
		end
	end

	return {materials = materials, occupancy = occupancy}
end

-- Bind the callback to be called in parallel execution context
actor:BindToMessageParallel("GenerateChunk", function(x, y, z, seed)
	local voxels = generateVoxelsWithSeed(x, y, z, seed)
	local corner = Vector3.new(x * 16, y * 16, z * 16)

	-- Currently, WriteVoxels() must be called in the serial phase
	task.synchronize()
	workspace.Terrain:WriteVoxels(
		Region3.new(corner, corner + Vector3.new(16, 16, 16)),
		4,
		voxels.materials,
		voxels.occupancy
	)
end)

On line 20, there is a task.defer(function() that has some code inside of it that will run to procedurally generate some randomly smooth terrain. This… is what is supposed to happen. The problem is that when you remove the task.defer, it won’t run. But if I put a print that is supposed to run after the block of code that is supposed to be inside of the task.defer, the print will print whatever I put inside of it, but the terrain won’t generate. What is happening here? Why does this code need to be deferred in order to run, but the print still runs?

It’s because the BindToMessageParallel is not yet initialized.

1 Like

When you clone a Script, it has to wait for the next opportunity in the task scheduler to run its code. Without deferring, those Actor messages wouldn’t have an associated callback yet since those cloned scripts haven’t had an opportunity to do their setup (bind to actor messages). In this case, the messages aren’t handled by the Actors so there is no terrain generation done.

1 Like

So what you are saying is that the :SendMessage("GenerateChunk", x, y, z, seed) runs before the worker scripts were able to bind to the "GenerateChunk" message?

@deleteables

Yes. It’s deferred to give each Actor’s script time to bind to that message topic.

1 Like

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