How can I optimize my fire spreading script?

I made this fire-spreading script for a project I’m working on with some others. The script picks a valid BasePart in the current map to be the source of the fire, then spreads the fire from there.

The script also deals with the lifetime of the fire, such as making it turn into ashes after a certain amount of time. The fire works similarly to the fire in Natural Disaster Survival.

Script Performance

image

Script

-- This code has since been rewritten

local CollectionService = game:GetService("CollectionService")
local PlayerService = game:GetService("Players")

local random = Random.new()

local currentMap = workspace.CurrentMap
local mapDescendants = currentMap:GetDescendants()

local fireColors = {"Bright yellow", "Bright orange"}
local ashColors = {"Black"}

local sourcePartCount = 1
local spreadCooldownRange = {0.025, 0.10}

local spreadQueue = {}

local function getSourceParts()
	local sourceParts = {}
	
	for index = 1, sourcePartCount do
		local sourcePart
		
		local noFire
		local isBasePart
		
		repeat task.wait()
			sourcePart = mapDescendants[random:NextInteger(1, #mapDescendants)]
			
			noFire = CollectionService:HasTag(sourcePart, "NoFire")
			isBasePart = sourcePart:IsA("BasePart")
		until isBasePart and not noFire
		
		table.insert(sourceParts, sourcePart)
	end
	
	return sourceParts
end

local function getNearbyParts(part)
	local nearbyParts = {}
	
	local fireParts = CollectionService:GetTagged("Fire")
	local ashParts = CollectionService:GetTagged("Ash")
	local noFire = CollectionService:GetTagged("NoFire")
	
	local size = part.Size
	local offset = Vector3.new(size.X + 0.55, size.Y + 0.55, size.Z + 0.55)

	local parameters = OverlapParams.new()

	parameters.FilterDescendantsInstances = {part, fireParts, ashParts, noFire}
	parameters.FilterType = Enum.RaycastFilterType.Exclude

	nearbyParts = workspace:GetPartBoundsInBox(part.CFrame, offset, parameters)
	
	return nearbyParts
end

local function addToLifetime(part)
	while task.wait(1) do
		local lifetime = part:GetAttribute("Lifetime") or 0
		part:SetAttribute("Lifetime", lifetime + 1)
	end
end

local function changePart(part, partType)
	if partType == "Fire" then
		table.insert(spreadQueue, part)
		
		local randomColor = random:NextInteger(1, #fireColors)
		local randomWait = random:NextInteger(20, 30)

		part.Material = Enum.Material.Neon
		part.BrickColor = BrickColor.new(fireColors[randomColor])

		CollectionService:AddTag(part, "Fire")
		coroutine.wrap(addToLifetime)(part)
	end

	if partType == "Ash" then
		local randomColor = random:NextInteger(1, #ashColors)

		part.Material = Enum.Material.Slate
		part.BrickColor = BrickColor.new(ashColors[randomColor])

		CollectionService:RemoveTag(part, "Fire")
		CollectionService:AddTag(part, "Ash")
	end
end

local function updateParts()
	local fireParts = CollectionService:GetTagged("Fire")
	local ashParts = CollectionService:GetTagged("Ash")
	
	for index, part in fireParts do
		local lifetime = part:GetAttribute("Lifetime") or 0
		
		if lifetime > random:NextInteger(10, 15) then
			if not part.Anchored then
				part:BreakJoints()
			end
		end
		
		if lifetime > random:NextInteger(40, 50) then
			changePart(part, "Ash")
		end
	end
	
	for index, part in ashParts do
		local lifetime = part:GetAttribute("Lifetime")
		
		if lifetime >  random:NextInteger(70, 80) then
			local weld = part:FindFirstChild("Weld")
			
			if weld then
				return
			end
			
			if part.Anchored then
				return
			end
			
			part:Destroy()
		end
	end
end

local function spread(source)
	local nearbyParts = getNearbyParts(source)
	
	for index, part in nearbyParts do
		local isDescendant = part:IsDescendantOf(currentMap)
		
		if not isDescendant then
			continue
		end
		
		changePart(part, "Fire")
	end
end

local sourceParts = getSourceParts()

for index, part in sourceParts do
	spread(part)
end

for index, part in spreadQueue do
	local rangeNumberOne = spreadCooldownRange[1]
	local rangeNumberTwo = spreadCooldownRange[2]

	local randomCooldown = random:NextNumber(rangeNumberOne, rangeNumberTwo)

	spread(part)
	updateParts()

	task.spawn(function()
		while true do
			local randomCooldown = random:NextNumber(2, 5)
			
			spread(part)
			updateParts()
			
			task.wait(randomCooldown)
		end
	end)

	task.wait(randomCooldown)
end
1 Like

The script honestly looks good. No amount of optimization would make a difference.

The problem is the activity is at 33% and the script does the same thing the fire in Natural Disaster Survival does.

It’ll be more complex, but you could try using a single thread for every fire-spreading part, instead of spawning a new one.

That wouldn’t work because the function i’m wrapping in a thread yields.

I figure it would still be possible if you were to do time counting manually. Would still be overly-complex & ugly.

Honestly, if this doesn’t really rely on any external tables and whatever else you could maybe hack in some rudimentary parallel luau support. Like maybe you could make a new parallel thread for each source part (i think the source parts are the parts that start the fire, yes?) and have those handle the fire spreading. Even if they don’t do any real serious parallel work and they just have copy pasted code, they should still be more performant than before. Not that much performant by default but still a decent uplift. You may be able to hack in some bigger performance gains with parallel luau if you can figure out a good way to handle the code handling actual instance changes and the code calculating and counting everything.

1 Like

Hello guys! I changed a few lines to make it so the code doesn’t have to rely on multi-threading and performance is much better. Thanks for your comments!

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