How can I make this block selection code more efficient?

I’m working on a voxel game, and I’m having trouble getting decent performance on my block selection code. I have an invisible part in CurrentCamera with a SelectionBox in it that I CFrame every RenderStep to the appropriate position if the player’s mouse has a target, and if the position isn’t the same as it was the previous frame. I thought that binding this to RenderStep was the problem, but changing it to an event that updates less frequently, like Mouse.Move, yields similar results. The FPS drop is only visible when the selected block changes, but it persists for a second or so after that. Using the Rendering panel (shift + F2), I saw that, when switching positions, the CPU timing goes up dramatically (from about ~12ms to ~32ms). For some context, the system functions in this way because my terrain is greedy meshed. In other words, simply switching the SelectionBox’s adornee isn’t an option. The part is anchored, CanCollide false, doesn’t cast a shadow, and is completely transparent. Commenting out the CFrame change eliminates any performance issues, but that’s obviously not a solution. Not really sure what I can do to solve this. Here’s the code:

RunService.RenderStepped:connect(function()
	local targetSurface = mouse.TargetSurface
	if targetSurface then
		local mouseHit = mouse.Hit
		local normal = normals[targetSurface]
		local currentBlock = Vector3.new(math.floor((mouseHit.x - normal.X) / 3) * 3 + 1.5, math.floor((mouseHit.y - normal.Y) / 3) * 3 + 1.5, math.floor((mouseHit.z - normal.Z) / 3) * 3 + 1.5)
		if currentBlock ~= previousBlock then
			selectionPart.CFrame = CFrame.new(currentBlock)
			selection.Visible = true
			previousBlock = currentBlock
		end
	elseif previousBlock ~= nil then
		selection.Visible = false
		previousBlock = nil
	end
end)
1 Like

I reverse engineered the rest of your code (because I’ve done this before) and I’m getting 0 performance drops. Nothing from the code indicates that I should either. Are you sure it’s this script? Here’s my code

local RunService = game:GetService("RunService")

local selectionPart = Instance.new("Part")
selectionPart.Anchored = true
selectionPart.CanCollide = false
selectionPart.Parent = workspace
selectionPart.Size = Vector3.new(3, 3, 3)

local Player = game.Players.LocalPlayer
local Mouse = Player:GetMouse()
Mouse.TargetFilter = selectionPart

local previousBlock = nil
local normals = {
	[Enum.NormalId.Back] = Vector3.new(0, 0, 1),
	[Enum.NormalId.Bottom] = Vector3.new(0, -1, 0),
	[Enum.NormalId.Front] = Vector3.new(0, 0, -1),
	[Enum.NormalId.Left] = Vector3.new(-1, 0, 0),
	[Enum.NormalId.Right] = Vector3.new(1, 0, 0),
	[Enum.NormalId.Top] = Vector3.new(0, 1, 0)
}

RunService.RenderStepped:connect(function()
	local targetSurface = Mouse.TargetSurface
	if targetSurface then
		local mouseHit = Mouse.Hit
		local normal = normals[targetSurface]
		local currentBlock = Vector3.new(math.floor((mouseHit.x - normal.X) / 3) * 3 + 1.5, math.floor((mouseHit.y - normal.Y) / 3) * 3 + 1.5, math.floor((mouseHit.z - normal.Z) / 3) * 3 + 1.5)
		if currentBlock ~= previousBlock then
			selectionPart.CFrame = CFrame.new(currentBlock)
			selectionPart.Transparency = 0
			previousBlock = currentBlock
		end
	elseif previousBlock ~= nil then
		selectionPart.Transparency = 1
		previousBlock = nil
	end
end)
3 Likes

Yeah, I’m confident. I tried commenting out the CFrame line, and something about that seems to tank performance. A few things I noticed:

  1. If Roblox Studio’s window isn’t in focus, the performance problems aren’t as extreme. No FPS drops, but CPU timing does still go up a decent amount.

  2. Performance is alright on my laptop with a pretty fast processor. On a slower (but still reasonably average) computer with an i3-7100u, performance drops from about 60 FPS to ~40 FPS.

  3. If I delete a block (which requires me to rerun my greedy meshing algorithm, reparent/resize 40+ parts, and change their CFrames using BulkMoveTo), changing the selection part’s CFrame, there are virtually no performance drops. However, simply moving my mouse to move the selection part does. Very strange.

Also, nice job reverse engineering my code, I had to double check to see if I posted the entire snippet.

1 Like

Setting a single CFrame won’t tank performance, and your specs are fine. By itself, the code works fine. My only guess is that something is trying to interact with that part and causing it to bork. Are you doing any Region3s or GetTouchingParts()?

EDIT: Is your greedy mesher trying to remesh every frame when your mouse hovers? Idk how your system works but it’s a guess.

3 Likes

No Region3s or GetTouchingParts(), and I triple-checked to make sure that the greedy mesher only ran when needed. I suspect that this might be rendering or physics-related. When I simply move the mouse around to change the selection part’s CFrame, the performance issues appear. When I do the exact same thing and click (to delete a block, running the greedy meshing code), no performance issues. It’s very strange.

I’ve attached a baseplate with the code. CPU timing goes from about 13ms when there’s no mouse movement to 26ms when moving the mouse around quickly (on my slower PC).
blockselector.rbxl (21.5 KB)

This is truly bizarre. CFraming the part every frame causes my FPS to drop from ~250 to ~200. And it’s not even your script. Just CFraming any part every frame is having the same effect.

I used the microprofiler to track how long the function is taking to run, and it’s very quick. What’s changing is the Rendering task apparently. Even if the part isn’t parented to workspace, it still affects the Render task. I honestly have no idea what’s going on.

2 Likes

Well, at the very least this sounds like an engine bug that can be fixed. I was getting worried that there’d be no solution whatsoever.

Can you try binding the function to RunService.Heartbeat? Kind of curious to see if there’s any performance differences between RenderStepped and Heartbeat.

It’s wouldn’t really be a matter of “performance”, it’s a matter of when they fire. Either before a frame, or after a frame.

Yeah I tried both. No difference.

1 Like

What’s really strange is that I’m not even changing the CFrame every frame. It’s only when the block that’s supposed to be selected changes that this happens. A single update to the CFrame is causing this drop, which makes absolutely no sense to me.

1 Like

At this point I’m completely stumped. Is changing a CFrame just supposed to be that intensive of a task? Simply changing the SelectionBox’s adornee won’t work for the reason mentioned in the original post. I don’t know what else to do other than change the CFrame of a block. If SelectionBox had an offset that would work, but obviously that’s not the case. Not sure what to do.

1 Like