Code Review: Factory System

Hello!
I just wanted to see if there is any flaws in my factory code. I am trying to improve bad habits.

local main = require(script.Parent:FindFirstChild("FactoryAssets"))
script.Parent.Parent.TotalAssets.Changed:Connect(function()
	for i, v in pairs(script.Parent.Parent.FactoryPlots:GetChildren()) do
		if v:IsA("Model") then
			if v.FactoryType.Value == "Bread" then
				-----BREAD-----
				if v.Factory.Full.Value == false and main.WheatCrops >= 5 then
					main.WheatCrops -= 5
					v.Factory.Full.Value = true
					main.Bread += 1
				end
			elseif v.FactoryType.Value == "Scythe" then
				-----SCYTHE-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 4 then
						main.Wood -= 4
						v.Factory.Full.Value = true
						main.WoodScythe += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
						main.Wood -= 3
						main.Steal -= 1
						v.Factory.Full.Value = true
						main.SteelScythe += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
						main.Wood -= 3
						main.Titanium -= 1
						v.Factory.Full.Value = true
						main.TitaniumScythe += 1
					end
				end
			elseif v.FactoryType.Value == "Axe" then
				-----AXE-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 4 then
						main.Wood -= 4
						v.Factory.Full.Value = true
						main.WoodAxe += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
						main.Wood -= 3
						main.Steal -= 1
						v.Factory.Full.Value = true
						main.SteelAxe += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
						main.Wood -= 3
						main.Titanium -= 1
						v.Factory.Full.Value = true
						main.TitaniumAxe += 1
					end
				end
			elseif v.FactoryType.Value == "Hammer" then
				-----HAMMER-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 4 then
						main.Wood -= 4
						v.Factory.Full.Value = true
						main.WoodHammer += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
						main.Wood -= 3
						main.Steal -= 1
						v.Factory.Full.Value = true
						main.SteelHammer += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
						main.Wood -= 3
						main.Titanium -= 1
						v.Factory.Full.Value = true
						main.TitaniumHammer += 1
					end
				end
			elseif v.FactoryType.Value == "Sword" then
				-----SWORD-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 4 then
						main.Wood -= 4
						v.Factory.Full.Value = true
						main.WoodSword += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
						main.Wood -= 3
						main.Steal -= 1
						v.Factory.Full.Value = true
						main.SteelSword += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
						main.Wood -= 3
						main.Titanium -= 1
						v.Factory.Full.Value = true
						main.TitaniumSword += 1
					end
				end
			elseif v.FactoryType.Value == "Fishing" then
				-----FISHING-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 4 then
						main.Wood -= 4
						v.Factory.Full.Value = true
						main.WoodFishingPole += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
						main.Wood -= 3
						main.Steal -= 1
						v.Factory.Full.Value = true
						main.SteelFishingPole += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
						main.Wood -= 3
						main.Titanium -= 1
						v.Factory.Full.Value = true
						main.TitaniumFishingPole += 1
					end
				end
			elseif v.FactoryType.Value == "Armor" then
				-----ARMOR-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 4 then
						main.Wood -= 4
						v.Factory.Full.Value = true
						main.WoodArmor += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
						main.Wood -= 3
						main.Steal -= 1
						v.Factory.Full.Value = true
						main.SteelArmor += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
						main.Wood -= 3
						main.Titanium -= 1
						v.Factory.Full.Value = true
						main.TitaniumArmor += 1
					end
				end
			elseif v.FactoryType.Value == "Explosives" then
				-----EXPLOSIVES-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel == 2 then
						main.Wood -= 2
						main.Steel -= 2
						v.Factory.Full.Value = true
						local numb = math.random(1, 3)
						if numb == 1 then
							main.BasicBomb += 1
						elseif numb == 2 then
							main.BasicC4 += 1
						elseif numb == 3 then
							main.BasicRocketLauncher += 1
						end
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel == 3 then
						main.Wood -= 1
						main.Steel -= 3
						v.Factory.Full.Value = true
						local numb = math.random(1, 3)
						if numb == 1 then
							main.GoodBomb += 1
						elseif numb == 2 then
							main.GoodC4 += 1
						elseif numb == 3 then
							main.GoodRocketLauncher += 1
						end
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium == 2 then
						main.Wood -= 2
						main.Titanium -= 2
						v.Factory.Full.Value = true
						local numb = math.random(1, 3)
						if numb == 1 then
							main.PowerfulBomb += 1
						elseif numb == 2 then
							main.PowerfulC4 += 1
						elseif numb == 3 then
							main.PowerfulRocketLauncher += 1
						end
					end
				end
			elseif v.FactoryType.Value == "Gun" then
				-----GUN-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel >= 2 then
						main.Wood -= 2
						main.Steel -= 2
						v.Factory.Full.Value = true
						main.BasicGun += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
						main.Wood -= 1
						main.Steal -= 3
						v.Factory.Full.Value = true
						main.GoodGun += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium >= 2 then
						main.Wood -= 2
						main.Titanium -= 2
						v.Factory.Full.Value = true
						main.PowerfulGun += 1
					end
				end
			elseif v.FactoryType.Value == "Meat" then
				-----MEAT-----
				if v.Factory.Full.Value == false and main.FarmingMeat >= 2 then
					main.FarmingMeat -= 2
					v.Factory.Full.Value = true
					main.Meat += 1
				end
			elseif v.FactoryType.Value == "Mech" then
				-----MECH-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel >= 2 then
						main.Wood -= 2
						main.Steel -= 2
						v.Factory.Full.Value = true
						main.BasicMech += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
						main.Wood -= 1
						main.Steal -= 3
						v.Factory.Full.Value = true
						main.GoodMech += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium >= 2 then
						main.Wood -= 2
						main.Titanium -= 2
						v.Factory.Full.Value = true
						main.PowerfulMech += 1
					end
				end
			elseif v.FactoryType.Value == "Tank" then
				-----MINI TANK-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel >= 2 then
						main.Wood -= 2
						main.Steel -= 2
						v.Factory.Full.Value = true
						main.BasicTank += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
						main.Wood -= 1
						main.Steal -= 3
						v.Factory.Full.Value = true
						main.GoodTank += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium >= 2 then
						main.Wood -= 2
						main.Titanium -= 2
						v.Factory.Full.Value = true
						main.PowerfulTank += 1
					end
				end
			elseif v.FactoryType.Value == "Mortar" then
				-----MORTAR-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel >= 2 then
						main.Wood -= 2
						main.Steel -= 2
						v.Factory.Full.Value = true
						main.BasicMortar += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
						main.Wood -= 1
						main.Steal -= 3
						v.Factory.Full.Value = true
						main.GoodMortar += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium >= 2 then
						main.Wood -= 2
						main.Titanium -= 2
						v.Factory.Full.Value = true
						main.PowerfulMortar += 1
					end
				end
			elseif v.FactoryType.Value == "Rice" then
				-----RICE-----
				if v.Factory.Full.Value == false and main.RiceCrops >= 5 then
					main.RiceCrops -= 5
					v.Factory.Full.Value = true
					main.Rice += 1
				end
			elseif v.FactoryType.Value == "Rifle" then
				-----RIFLE-----
				if v.Factory.FactoryLevel.Value == 1 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel >= 2 then
						main.Wood -= 2
						main.Steel -= 2
						v.Factory.Full.Value = true
						main.BasicRifle += 1
					end
				elseif v.Factory.FactoryLevel.Value == 2 then
					if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
						main.Wood -= 1
						main.Steal -= 3
						v.Factory.Full.Value = true
						main.GoodRifle += 1
					end
				elseif v.Factory.FactoryLevel.Value == 3 then
					if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium >= 2 then
						main.Wood -= 2
						main.Titanium -= 2
						v.Factory.Full.Value = true
						main.PowerfulRifle += 1
					end
				end
			elseif v.FactoryType.Value == "Robot" then
					-----ROBOT-----
					if v.Factory.FactoryLevel.Value == 1 then
						if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
							main.Wood -= 1
							main.Steel -= 3
							v.Factory.Full.Value = true
							main.BasicRobot += 1
						end
					elseif v.Factory.FactoryLevel.Value == 2 then
						if v.Factory.Full.Value == false and main.Steel >= 4 then
							main.Steal -= 4
							v.Factory.Full.Value = true
							main.IntermediateRobot += 1
						end
					elseif v.Factory.FactoryLevel.Value == 3 then
						if v.Factory.Full.Value == false and main.Steel >= 2 and main.Titanium >= 2 then
							main.Steel -= 2
							main.Titanium -= 2
							v.Factory.Full.Value = true
							main.AdvancedRobot += 1
						end
			elseif v.FactoryType.Value == "Shield" then
					-----SHIELD-----
					if v.Factory.FactoryLevel.Value == 1 then
						if v.Factory.Full.Value == false and main.Wood >= 4 then
							main.Wood -= 4
							v.Factory.Full.Value = true
							main.WoodShield += 1
						end
					elseif v.Factory.FactoryLevel.Value == 2 then
						if v.Factory.Full.Value == false and main.Wood >= 3 and main.Steel >= 1 then
							main.Wood -= 3
							main.Steal -= 1
							v.Factory.Full.Value = true
							main.SteelShield += 1
						end
					elseif v.Factory.FactoryLevel.Value == 3 then
						if v.Factory.Full.Value == false and main.Wood >= 3 and main.Titanium >= 1 then
							main.Wood -= 3
							main.Titanium -= 1
							v.Factory.Full.Value = true
							main.TitaniumShield += 1
						end
					end
			elseif v.FactoryType.Value == "Turret" then
					-----TURRET-----
					if v.Factory.FactoryLevel.Value == 1 then
						if v.Factory.Full.Value == false and main.Wood >= 2 and main.Steel >= 2 then
							main.Wood -= 2
							main.Steel -= 2
							v.Factory.Full.Value = true
							main.BasicTurret += 1
						end
					elseif v.Factory.FactoryLevel.Value == 2 then
						if v.Factory.Full.Value == false and main.Wood >= 1 and main.Steel >= 3 then
							main.Wood -= 1
							main.Steal -= 3
							v.Factory.Full.Value = true
							main.GoodTurret += 1
						end
					elseif v.Factory.FactoryLevel.Value == 3 then
						if v.Factory.Full.Value == false and main.Wood >= 2 and main.Titanium >= 2 then
							main.Wood -= 2
							main.Titanium -= 2
							v.Factory.Full.Value = true
							main.PowerfulTurret += 1
						end
					end
				end
			end
		end
	end
end)
5 Likes

If you keep using if statements and elseif statements it gets kind of messy. But since your using comments it’s fine.

Quick question because this seems faster.
Are these things the sane.

Thingy.Value += 1
Thingy.Value = Thingy.Value + 1

Are these the same because this seems much faster.

yes they are the same. Roblox added that as a new feature.

Thanks! I didn’t know about this feature.

They’re recently added (about a few months ago). Compound operators are pretty neat.

There’s a few more but they aren’t documented.

1 Like

Thanks! Understand this much more.

1 Like

You’ve got a perfect opportunity to get rid of your hard-coded mile-long if-else chains and replace it with a more data-driven approach. Every factory consumes something and produces something. The different types only differ in what and how much gets consumed. That’s a kind of data, and in this case it’s simple to extract that data, store it separately, and create some code that acts on it in a way that’s way simpler than 400 lines of if-else.

I'm going to start with the simplest case, and keep adding to it until we get something that can handle all your special cases.

We can store all the info needed to make a factory produce once in a dictionary like so:

local factoryInfos = {
	Bread = {
		consumes = {WheatCrops = 5},
		produces = {Bread = 1}
	},
}

Here’s the code that would enable us to handle e.g. Bread factories:

script.Parent.Parent.TotalAssets.Changed:Connect(function()
	for i, v in pairs(script.Parent.Parent.FactoryPlots:GetChildren()) do
		if v:IsA("Model") then
			--Check that we're not full
			if v.Factory.Full.Value then
				continue
			end

			--Find data about consumption and production
			local factoryInfo = factoryInfos[v.FactoryType.Value]

			--Check if there's enough resources to consume
			local hasNeededResouces = true 
			for consumedName, consumedAmount in pairs(factoryInfo.consumes) do
				if main[consumedName] < consumedAmount then
					hasNeededResouces = false
					break
				end
			end

			--Skip this factory plot if we don't
			if not hasNeededResouces then 
				continue 
			end

			--Consume and produce if we do
			for consumedName, consumedAmount in pairs(factoryInfo.consumes) do
				main[consumedName] -= consumedAmount
			end

			for proucedName, producedAmount in pairs(factoryInfo.produces) do
				main[producedName] += producedAmount
			end

			v.Factory.Full.Value = true
		end
	end
end)

However, many factory types have different levels that produce different amounts. We can fix the info dict like so:

local factoryInfos = {
	Bread = {
		consumes = {WheatCrops = 5},
		produces = {Bread = 1}
	},
	Scythe1 = {
		consumes = {Wood = 4},
		produces = {WoodScythe = 1}
	},
	Scythe2 = {
		consumes = {Wood = 3, Steal = 1},
		produces = {SteelScythe = 1}
	},
}

and the handling code like this:

--Find data about consumption and production
local factoryLevel = v.Factory:FindFirstChild("FactoryLevel")
local factoryTypeName = v.FactoryType.Value .. (factoryLevel and factoryLevel.Value or "") --read up on ternary statements if this is confusing
local factoryInfo = factoryInfos[factoryTypeName]

But wait! Some of the factories produce a randomly selected item! We can fix the data dict by doing like so:

--add to infos dict
Explosives1 = {
	consumes = {Wood = 2, Steel = 2},
	produces = function()
		local roll = math.random(1, 3)
		if roll == 1 then
			return {BasicBomb = 1}
		elseif roll == 2 then
			return {BasicC4 = 1}
		elseif roll == 3 then
			return {BasicRocketLauncher = 1}
		end
	end
}

And the handling code like this:

local consumes = typeof(factoryInfo.consumes == "table") and factoryInfo.consumes or factoryInfo.consumes()
local produces = typeof(factoryInfo.produces == "table") and factoryInfo.produces or factoryInfo.produces()
			
for consumedName, consumedAmount in pairs(consumes) do
	if main[consumedName] < consumedAmount then
		hasNeededResouces = false
		break
	end
end

--Skip this factory plot if we don't
if not hasNeededResouces then 
	continue 
end

--Consume and produce if we do
for consumedName, consumedAmount in pairs(consumes) do
	main[consumedName] -= consumedAmount
end

for proucedName, producedAmount in pairs(produces) do
	main[producedName] += producedAmount
end

By replacing the constant dictionaries with functions, we can generate any production or consumption on-the-fly, no matter how complicated you want to make it. This way makes the infos dict kind of ugly tho, especially when lots of factory types use a randomly picked production. Since all of then choose one from a list with equal probability, we can make a function that returns functions that choose from a list. Like so:

function randomChooserMaker(list)
	return function()
		local roll = math.random(1, #list)
		return list[roll]
	end
end

...

Explosives1 = {
	consumes = {Wood = 2, Steel = 2},
	produces = randomChooserMaker({
		{BasicBomb = 1}, {BasicC4 = 1}, {BasicRocketLauncher = 1}
	})
}

Here's all the example data, you'll have to fill in the rest
function randomChooserFactory(list)
	return function()
		local roll = math.random(1, #list)
		return list[roll]
	end
end

local factoryInfos = {
	Bread = {
		consumes = {WheatCrops = 5},
		produces = {Bread = 1}
	},
	Scythe1 = {
		consumes = {Wood = 4},
		produces = {WoodScythe = 1}
	},
	Scythe2 = {
		consumes = {Wood = 3, Steal = 1},
		produces = {SteelScythe = 1}
	},
	Explosives1 = {
		consumes = {Wood = 2, Steel = 2},
		produces = randomChooserMaker({
			{BasicBomb = 1}, {BasicC4 = 1}, {BasicRocketLauncher = 1}
		})
	}
}
Here's the code for handling a factory plot:
script.Parent.Parent.TotalAssets.Changed:Connect(function()
	for i, v in pairs(script.Parent.Parent.FactoryPlots:GetChildren()) do
		if v:IsA("Model") then
			--Check that we're not full
			if v.Factory.Full.Value then
				--Skip if we are
				continue
			end

			--Find data about consumption and production
			local factoryLevel = v.Factory:FindFirstChild("FactoryLevel")
			local factoryTypeName = v.FactoryType.Value .. (factoryLevel and factoryLevel.Value or "")
			local factoryInfo = factoryInfos[factoryTypeName]

			--Check if there's enough resources to consume...
			local hasNeededResouces = true
			local consumes = typeof(factoryInfo.consumes == "table") and factoryInfo.consumes or factoryInfo.consumes() --if it's not a table, it's a function that returns a table
			local produces = typeof(factoryInfo.produces == "table") and factoryInfo.produces or factoryInfo.produces() --these are ternaries again

			--Check each consumed resource
			for consumedName, consumedAmount in pairs(consumes) do
				if main[consumedName] < consumedAmount then
					hasNeededResouces = false
					break --No need to check the other resources if we already found one we can't afford
				end
			end

			-- ...skip this factory plot if we don't
			if not hasNeededResouces then 
				continue 
			end

			--Consume and produce if we do
			for consumedName, consumedAmount in pairs(consumes) do
				main[consumedName] -= consumedAmount
			end

			for proucedName, producedAmount in pairs(produces) do
				main[producedName] += producedAmount
			end

			v.Factory.Full.Value = true
		end
	end
end)

The advantages of this approach are clear. It’s a lot less typing for you when you’re writing the code, and a lot less scrolling when you want to tweak something and read the code. It’s also a lot less error prone. You’ve got spelling mistakes in a lot of places, like “Steal” instead of “Steel”. When you come back later and tweak the costs, you’re likely to forget to also modify the checks to make sure the factory can afford to consume the things. Because that info is only typed out in one single place, it’s impossible to accidentally forgot to change something somewhere, because there’s only one place to modify it - in the data table.

Plus it’s just kind of cool.

I hope this helps. Let me know if something is confusing or doesn’t work :slight_smile:

Oh, and there might be some types or somthing. I can’t test the code obviously, but if you get errors then post those and we’ll get it working.

15 Likes

wow thanks for your time in putting that together :smile:

1 Like

Also, Im pretty sure it is spelled “steel”
and randomChooserMaker is not a function so it does not work.
Also, the function produces and consumes give me an error message. What are they for?

They are functions that determine what a factory type consumes and produces. Normally it’s a table because most factories always consume and produce the same thing, but sometimes it’s a function because it has to choose what to produce every time.

I added the randomChooserMaker function

Kingdom.MainScripts.FactoryManagement:236: attempt to index nil with 'consumes'

I get this…

Try adding this before the --check that we're not full part:

local factoryInfo = factoryInfos[factoryTypeName]
assert(factoryInfo, string.format("Couldn't find factoryInfo for %s", tostring(factoryTypeName)))

It’ll tell you which factory type cause the error. It seems like one of the factory types isn’t defined in the FactoryInfo dict. Could be a typo somewhere

Found the problem, it wants a Bread1 instead of Bread. I will try to fix the if statement for the or "

1 Like

Calling assert function to check if a variable exists is slow when u can just do the same thing with lua idioms / branchless programming.

Just do this:

factoryInfo = factoryInfo or error(("Couldn't find factoryInfo for %s"):format(factoryTypeName));
1 Like

How did I go my entire life not knowing you can do this?! Thanks! :smiley: Also cool performance tip, thanks for that as well. Do you know how much of a difference it makes performance-wise?

I mean whether or not you use string formatting or concatenation doesn’t matter. But if you were talking about performance differences between assert and lua idioms. There is definitely a difference.

Explanation: You can do the same things as assert without calling a subroutine which takes more time for luau interpreter to process meaning lua idioms are faster. Assert is just an auxiliary function.

A more complex usage of lua idioms over assert:

-- Bad / Slow
local variable = 5;
assert(type(variable) == 'number', 'Variable must be a number!');

-- Good / Efficient
local variable1 = 5;
variable1 = (type(variable1)  == 'number' and variable1) or error('Variable must be a number!');

As a preliminary pedantic correction, what is getting described as Lua idioms is not idiomatic Lua code; instead, it merely is “short-circuit evaluation” (In fact, the “assert” function is considered idiomatic Lua code more so than lazy evaluation).

Now then: Yes, that short-circuit evaluation is more performant (negligibly in most cases as often a string literal is evaluated—a relatively cheap operation). No, converting any assertion to this short-circuiting alternative is not good advice.

An excellent way to visualize this is by comparing the pros and cons; here are the pros:

  1. Evaluation of the assertion message only happens when necessary.
  2. Control over which frame in the call stack from which the error gets reported.

However, here are the cons:

  1. Decreased readability.
  2. Decreased maintainability.
  3. These assignments get flagged as errors by modern linters.
  4. These assignments get flagged by Luau’s static type checker.

Besides the fact that the pros introduced are often insignificant, introducing this extra layer of complexity and mental overhead is completely discouraged, especially when it comes at the expense of production tooling. While there are ways around it, this is a high-friction pattern in a programming language; a humorous oxymoron if anything else—an utterly premature optimization.

2 Likes

Yes, there is a negligible boost when using concatenation. Formatting strings on the other hand is a bit slower because not only are you calling a function, luau also checks if the pattern provided is correct when formatting and that is one of the reasons it’s slower compared to concatenation. The only reason people format strings is because it looks cleaner.

local a = ("hi this is %d"):format("a") --> error, %d expects a number not a string
1 Like