Multi-threading

how could I make this script multi-threaded using actors?
I’m trying to make a pixel raytracer which performs semi-well.

--!native
local ScreenGui = script.Parent
local Canvas = ScreenGui:WaitForChild("Frame")
local RunService = game:GetService("RunService")

-- Pixel size
local pixelSize = 8

local pixels = {}
local canvasWidth, canvasHeight

local frameCount = 0
local lastUpdateTime = tick()

local function initializePixels()
	canvasWidth = math.floor(Canvas.AbsoluteSize.X / pixelSize)
	canvasHeight = math.floor(Canvas.AbsoluteSize.Y / pixelSize)

	for _, pixelRow in pairs(Canvas:GetChildren()) do
		pixelRow:Destroy()
	end

	pixels = {}

	for y = 0, canvasHeight - 1 do
		pixels[y] = {}
		for x = 0, canvasWidth - 1 do
			local pixel = Instance.new("Frame")
			pixel.Size = UDim2.new(0, pixelSize, 0, pixelSize)
			pixel.Position = UDim2.new(0, x * pixelSize, 0, y * pixelSize)
			pixel.BorderSizePixel = 0
			pixel.BackgroundColor3 = Color3.new(0, 0, 0)
			pixel.Parent = Canvas
			pixels[y][x] = {
				frame = pixel,
				color = Color3.new(0, 0, 0)
			}
		end
	end
end

local function getColor(hit)
	if hit then
		return hit.BrickColor.Color
	else
		return Color3.new(0, 0, 0)
	end
end

-- Function to cast a ray and get the color
local function raytrace(camera, x, y, aspectRatio, fov)
	local screenX = (x / canvasWidth) * 2 - 1
	local screenY = (y / canvasHeight) * 2 - 1
	screenX = screenX * aspectRatio * math.tan(fov / 2)
	screenY = screenY * math.tan(fov / 2)
	local rayDirection = (camera.CFrame.LookVector + camera.CFrame.RightVector * screenX - camera.CFrame.UpVector * screenY).Unit
	local ray = Ray.new(camera.CFrame.Position, rayDirection * 100)
	local hit, hitPosition = game.Workspace:FindPartOnRay(ray)
	return getColor(hit)
end

local function updatePixels()
	local camera = game.Workspace.CurrentCamera
	local aspectRatio = canvasWidth / canvasHeight
	local fov = math.rad(120)

	local y = 0
	local function updateRow()
		for x = 0, canvasWidth - 1 do
			local color = raytrace(camera, x, y, aspectRatio, fov)
			if color ~= pixels[y][x].color then
				pixels[y][x].color = color
				pixels[y][x].frame.BackgroundColor3 = color
			end
		end
		y = y + 1
		if y < canvasHeight then
			RunService.RenderStepped:Wait()
			updateRow()
		else
			frameCount = frameCount + 1
			local currentTime = tick()
			if currentTime - lastUpdateTime >= 1 then
				local fps = frameCount / (currentTime - lastUpdateTime)
				ScreenGui.FPSCounter.Text = "FPS: " .. math.floor(fps + 0.5)
				frameCount = 0
				lastUpdateTime = currentTime
			end
		end
	end

	updateRow()
end

initializePixels()

RunService.RenderStepped:Connect(updatePixels)

Canvas:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
	initializePixels()
end)

(yes ik its ai generated, I’m just trying stuff out)

1 Like

Try using the spawn(function(), i think this can be really useful, but i’m not gonna give a example code now, because i’m lazy but it’s okay

Its single-threaded, its just make event sync like you do multiple things at once. Also use coroutine instead of this because its suck!
If you really want true multi-threading then you use Parallel Luau, a bit advance for you because you are using AI tho. I can explain further if you needed to

i have made the script parallel lua, but im having a bit of an issue, if i put my workers at 2, only 1/2 of the screen is rendered, if i put it at 8 only 1/8th and so on, do you know how to fix this?

code:

--!native
local ScreenGui = script.Parent

if ScreenGui:IsA("Actor") then
	ScreenGui = ScreenGui.Parent.Parent
end

local Canvas = ScreenGui:WaitForChild("Frame")
local RunService = game:GetService("RunService")

local pixelSize = 10

local pixels = {}
local canvasWidth, canvasHeight

local function initializePixels()
	canvasWidth = math.floor(Canvas.AbsoluteSize.X / pixelSize)
	canvasHeight = math.floor(Canvas.AbsoluteSize.Y / pixelSize)

	for _, pixelRow in pairs(Canvas:GetChildren()) do
		pixelRow:Destroy()
	end

	pixels = {}

	for y = 0, canvasHeight - 1 do
		pixels[y] = {}
		for x = 0, canvasWidth - 1 do
			local pixel = Instance.new("Frame")
			pixel.Size = UDim2.new(0, pixelSize, 0, pixelSize)
			pixel.Position = UDim2.new(0, x * pixelSize, 0, y * pixelSize)
			pixel.BorderSizePixel = 0
			pixel.BackgroundColor3 = Color3.new(0, 0, 0)
			pixel.Parent = Canvas
			pixels[y][x] = {
				frame = pixel,
				color = Color3.new(0, 0, 0)
			}
		end
	end
end

local function getColor(hit)
	if hit then
		return hit.Instance.BrickColor.Color
	else
		return Color3.new(0, 0, 0)
	end
end

local function raytrace(camera, x, y, aspectRatio, fov)
	local screenX = (x / canvasWidth) * 2 - 1
	local screenY = (y / canvasHeight) * 2 - 1
	screenX = screenX * aspectRatio * math.tan(fov / 2)
	screenY = screenY * math.tan(fov / 2)
	local rayDirection = (camera.CFrame.LookVector + camera.CFrame.RightVector * screenX - camera.CFrame.UpVector * screenY).Unit
	local ray = Ray.new(camera.CFrame.Position, rayDirection * 100)
	local hit = game.Workspace:Raycast(camera.CFrame.Position, rayDirection * 100)

	return getColor(hit)
end

local function updatePixels(startY, endY)
	local camera = game.Workspace.CurrentCamera
	local aspectRatio = canvasWidth / canvasHeight
	local fov = math.rad(90)

	for y = startY, endY do
		for x = 0, canvasWidth - 1 do
			local color = raytrace(camera, x, y, aspectRatio, fov)
			if color ~= pixels[y][x].color then
				pixels[y][x].color = color
				task.synchronize()
				pixels[y][x].frame.BackgroundColor3 = color
				task.desynchronize()
			end
		end
	end
end

initializePixels()

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

	for _, actor in ipairs(workers) do
		actor.Parent = script
	end

	while true do
		task.wait()
		for i, worker in ipairs(workers) do
			local startY = (i - 1) * math.ceil(canvasHeight / numWorkers)
			local endY = math.min(startY + math.ceil(canvasHeight / numWorkers) - 1, canvasHeight - 1)
			worker:SendMessage("Raycast", startY, endY)
		end
	end

	return
end

actor:BindToMessageParallel("Raycast", function(startY, endY)
	updatePixels(startY, endY)
end)

Canvas:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
	initializePixels()
end)

I cant help you with the scripts because im not an expert on pixel raytracer. All I can really help is give you some idea to work with.
Seem like the code is only handle the one part of the renderer.
In parallel Luau you need split works to your actors, so 2 actors handle 2 area 1 and 2.
You can Instance the Actor and give them their list of job, Actor 1 will handle area 1. You make them run every frame to render their area.

The way to do is is to utilize “worker scripts”.

Essentially, you’ve got your main script that does all of the important work like setting up things, setting properties and responding to player input, etc.

Your worker scripts are all located inside actors in some folder.
What you might wanna do here is make sure every actor has both a script and a bindable event or function object that you can invoke.

script.Parent.BindableEvent.Event:ConnectParallel(function(args)
 -- Execute your multi-threaded logic here.
end)

Scripts that are inside actors cannot communicate directly with other scripts so you likely WILL have to utilize something like a BindableEvent or a BindableFunction.

And your actor scripts also need a way to RETURN the value that they calculated for it to have any effect, this value you likely want to return to your main script so you can set properties and manipulate game objects.

do you know how to fix the script that i posted in my reply to C_Corpze?

call task.desynchronize to create a new thread
calling too many of these diminishes performance, so every x amount of raytraces you make a new thread
and call task.synchronize to be able to make write to properties again