Any suggestions on how to optimize code?

Currently a custom font renderer I’m working on is even with optimizations very slow, so I’m looking for help to find ways to optimize it further. Currently I’m using object pooling and pre-defined variables. So here’s the code and incase you guys can help please tell me. (I know it’s broken, it’ll be fixed by the time you see this post)

local letters = string.split([[0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!№;%:?*()_+-=.,/|"'@#$^&{}[]
]], "")

letters[63] = nil

--print(#letters)

local module = {}

local containers = {}

function module:Unapply(label: TextLabel)
	local container = containers[label]
	if container then
		container:Destroy()
		containers[label] = nil
		label.MaxVisibleGraphemes = -1
		warn("Font unapplied successfully!")
	else
		warn("Unable to unapply font.")
	end
end

function module:Apply(label: TextLabel, font)
	local POOLT = {}
	local mod = game.ReplicatedStorage.Fonts:FindFirstChild(font)
	if not mod then warn("Font not found.") return end
	local ancestorGui = label:FindFirstAncestorWhichIsA("ScreenGui")
	if not ancestorGui then warn("Label is not a descendant of ScreenGui.") return end
	local ignoreGuiInset = ancestorGui.IgnoreGuiInset
	local required = require(mod)
	local imageId = required[1]
	local font = required[2]
	local info = font.font.info
	local face = info._face
	local bold = info._bold
	local italic = info._italic
	local fontSize = tonumber(info._size) * 1.3
	local lineHeight = tonumber(font.font.common._lineHeight)
	local imgSize = Vector2.new(
		tonumber(font.font.common._scaleH),
		tonumber(font.font.common._scaleW)
	)
	local mul = label.TextSize / fontSize
	--warn(mul)
	local characters = {}
	for i, v in pairs(font.font.chars.char) do
		if tonumber(v._id) < 1000 then
			local char = string.char(v._id)
			local rectoffset = Vector2.new(tonumber(v._x), tonumber(v._y))
			local rectsize = Vector2.new(tonumber(v._width), tonumber(v._height))
			local offset = Vector2.new(tonumber(v._xoffset), tonumber(v._yoffset))
			characters[char] = {rectoffset, rectsize, offset, tonumber(v._xadvance)}
		end
	end
	label.MaxVisibleGraphemes = 0
	local gui = game.Players.LocalPlayer.PlayerGui:FindFirstChild("Fontinator_Gui")
	if not gui then
		gui = Instance.new("ScreenGui", game.Players.LocalPlayer.PlayerGui)
		gui.Name = "Fontinator_Gui"
		gui.ResetOnSpawn = false
		gui.IgnoreGuiInset = true
	end
	local container = Instance.new("Frame", gui)
	container.Name = label.Name .. "_Container"
	container.Transparency = 1
	container.Active = false
	container.Interactable = false
	containers[label] = container
	local padding = Instance.new("UIPadding", container)
	local function write(text: string, isPlaceholder)
		--container:ClearAllChildren()
		--local list = container:GetChildren()
		for i=1, #POOLT do
			local c = POOLT[i]
			if c:IsA("ImageLabel") then
				c.Visible = false
			end
		end
		--local split = string.split(text, "")
		local x = 0
		local y = 0
		local idx = 0
		local minX = 0
		local minY = 0
		local maxX = 0
		local maxY = 0
		local lastLine = -1
		local charss = {}
		local pool = 0
		local len = #text
		--print(len)
		local wrapped = label.TextWrapped
		local col = label.TextColor3
		local transp = label.TextTransparency
		local placeholderCol = label.PlaceholderColor3
		local cSize = container.AbsoluteSize
		for i = 1, len do
			local c = text:sub(i,i)
			local data = characters[c]
			idx += 1
			if data and (if (string.byte(c) == 32) then (lastLine ~= (idx-1)) else true) then
				pool += 1
				local img = POOLT[pool] or Instance.new("ImageLabel", container)
				POOLT[pool] = img
				img.Visible = true
				--if POOLT[pool] then
				--	img = 
				--else
				--	img = Instance.new("ImageLabel", container)
				--	POOLT[pool] = img
				--end
				--local 
				table.insert(charss, img)
				img.Image = imageId
				img.BackgroundTransparency = 1
				img.ImageTransparency = transp
				img.ImageColor3 = col
				if isPlaceholder then
					img.ImageColor3 = placeholderCol
				end
				img.Position = UDim2.fromOffset(x + (data[3].X * mul), y + (data[3].Y * mul))
				img.Size = UDim2.fromOffset(data[4] * mul, data[2].Y * mul)
				--print(img.AbsoluteSize)
				img.ResampleMode = Enum.ResamplerMode.Pixelated
				img.ScaleType = Enum.ScaleType.Fit
				img.ImageRectOffset = data[1]
				img.ImageRectSize = data[2]
				--if idx == #split then
				--	--warn("LAST CHARACTER!!!")
				--	maxX = (img.AbsolutePosition.X - container.AbsolutePosition.X) + img.AbsoluteSize.X
				--	maxY = (img.AbsolutePosition.Y - container.AbsolutePosition.Y) + img.AbsoluteSize.Y
				--end
				--local wrapped = label.TextWrapped
				local iSize = Vector2.new(data[4] * mul, data[2].Y * mul)
				--local iSize = img.AbsoluteSize
				x += iSize.X
				--x += data[4] * mul
				--if not  then return end
				if wrapped and ((x + iSize.X) > cSize.X) then
					x = 0
					y += lineHeight * mul
					lastLine = idx
				end
				--if label.TextWrapped and ((x > container.AbsoluteSize.X) or ((x + img.AbsoluteSize.X) > container.AbsoluteSize.X)) then
				--	x = 0
				--	y += lineHeight * mul
				--	lastLine = idx
				--else
				--	x += img.AbsoluteSize.X
				--end
			else
				--warn("unknown character", string.byte(c))
			end
			--task.wait()
		end
		--local padding = Instance.new("UIPadding", container)
		cSize = container.AbsoluteSize
		padding.PaddingTop = UDim.new(0, 0)
		padding.PaddingLeft = UDim.new(0, 0)
		padding.PaddingBottom = UDim.new(0, 0)
		padding.PaddingRight = UDim.new(0, 0)
		for i=1, #charss do
			local v = charss[i]
			if v.Visible then
				local x = ((v.AbsolutePosition.X - cSize.X) + v.AbsoluteSize.X)
				local y = ((v.AbsolutePosition.Y - cSize.Y) + v.AbsoluteSize.Y)
				if x > maxX then
					maxX = x
				end
				if y > maxY then
					maxY = y
				end
			end
		end
		local s = container.AbsoluteSize
		if label.TextYAlignment == Enum.TextYAlignment.Bottom then
			padding.PaddingTop = UDim.new(
				0,
				-(maxY - (s.Y))
			)
		elseif label.TextYAlignment == Enum.TextYAlignment.Center then
			padding.PaddingTop = UDim.new(
				0,
				-0.5 * (maxY - (s.Y))
			)
		elseif label.TextYAlignment == Enum.TextYAlignment.Top then
			padding.PaddingTop = UDim.new(0, 0)
			print("resetting Y")
		end
		if label.TextXAlignment == Enum.TextXAlignment.Center then
			padding.PaddingLeft = UDim.new(
				0,
				-0.5 * (maxX - (s.X))
			)
		elseif label.TextXAlignment == Enum.TextXAlignment.Right then
			padding.PaddingLeft = UDim.new(
				0,
				-(maxX - (s.X))
			)
		elseif label.TextXAlignment == Enum.TextXAlignment.Left then
			padding.PaddingLeft = UDim.new(0, 0)
			print("resetting X")
		end
		
	end
	local function update()
		mul = label.TextSize / fontSize
		local pos = label.AbsolutePosition
		local size = label.AbsoluteSize
		if ignoreGuiInset then
			container.Position = UDim2.fromOffset(pos.X, pos.Y)
		else
			container.Position = UDim2.fromOffset(pos.X, pos.Y + 58)
		end
		container.Size = UDim2.fromOffset(size.X, size.Y)
		container.Rotation = label.Rotation
		container.AnchorPoint = label.AnchorPoint
	end
	--task.spawn(function()
	--	while label.Parent and container.Parent do
	--		task.wait()
	--		update()
	--	end
	--end)
	update()
	if label:IsA("TextBox") then
		if #label.Text > 0 then
			write(label.Text)
		else
			write(label.PlaceholderText, true)
		end
	else
		write(label.Text)
	end
	local changes = {
		"TextSize",
		"Text",
		"TextColor3",
		"TextTransparency",
		"TextWrapped",
		"TextXAlignment",
		"TextYAlignment",
		"AbsoluteSize",
		"AbsolutePosition",
		"Rotation",
		"AnchorPoint"
	}
	for i=1, #changes do
		local c = changes[i]
		label:GetPropertyChangedSignal(c):Connect(function()
			update()
			if label:IsA("TextBox") then
				if #label.Text > 0 then
					write(label.Text)
				else
					write(label.PlaceholderText, true)
				end
			else
				write(label.Text)
			end
		end)
	end

	--label.Changed:Connect(function(prop)
	--	--print(prop)
	--	mul = label.TextSize / fontSize
	--	--if prop:find("Absolute") then return end
	--	if prop == "Rotation" then return end
	--	if prop == "TextBounds" then return end
	--	if prop == "TextFits" then return end
	--	if prop == "ContentText" then return end
	--	if prop == "GuiState" then return end
	--	if prop == "UniqueId" then return end
	--	if prop == "Name" then return end
	--	write(label.Text)
	--end)
end

function module:ApplyBillboard()

end

return module

Any help is appreciated!

I’m not the best algo guy, but a lot of this is as fast or nearly as fast as it can be, from my understanding. These suggestions range from micro-optimizations to potential fatal flaws.

tonumber is pretty slow. You should be storing these as numbers or cache this data. Maybe not the worst issue if you don’t run Apply a lot, I guess, but still should be cached.

Move local len = #text before this and use i = len. No wasteful redundant flipping of Visible after this. I’m pretty sure doing the c:IsA("ImageLabel") check here is redundant, too, so remove it.

Use table.create(len). Avoids resizing the table which can be slow.

Use string.sub(text, i, i). Will be marginally faster especially over the loop. But also, you should just use only string.byte(text, i, i). string.byte is a lot faster. Change characters to be a sequential, numeric array instead. This also improves speed very slightly. Also means you don’t need to do string.byte later, you can just use c.

Just use (string.byte(c) == 32 and lastLine ~= (idx-1)). Just a micro but it’s looped so it adds up.

This initialization should be moved to a function. This redundancy is wasting a lot of cycles doing metatable indexing multiple times. You should also parent after doing this initialization step, not before.

Don’t create a Vector2 here if you’re not going to use it. Just store the components in locals—way faster.

local iSize_X, iSize_Y = data[4] * mul, data[2].Y * mul
x += iSize_X
...

Using a single instance of UDim and sharing it may be marginally faster than creating a new one each time.

local UDIM_0 = UDim.new(0, 0)
padding.PaddingTop = UDIM_0
padding.PaddingLeft = UDIM_0
padding.PaddingBottom = UDIM_0
padding.PaddingRight = UDIM_0

Cache cSize’s components here in locals same as mentioned above. It’s looped so it’ll add up.

3 Likes

Ok thanks, I’ll try to implement these.

Using a single instance of UDim and sharing it may be marginally faster than creating a new one each time.

I know this was just because I was troubleshooting a problem so it was temporary

This has actually worked pretty well so I’m going to mark it as solution for now. Before, pasting 200 characters would slow it down very noticably but now it’s barely noticable at all.

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