Instances passed as nil in sorting function if not on screen

I’m trying to sort a table of instances for a camera targeting feature, and it works fine as long as they’re being looked at. The only problem I’m getting right now is that if I turn the camera away from the parts, they no longer exist. Here’s the function I’m using to sort them:

If I look in the opposite direction of anything in self.Targetable, either a or b becomes nil.
self.Targetable is a table defined as CollectionService:GetTagged("Targetable") that is updated ever 2 seconds.

if #self.Targetable >= 2 then
    table.sort(self.Targetable, function(a, b)
	-- print(a, b)
	if a == nil then return false end
	if b == nil then return true end
	if a == nil and b == nil then return true end
	local scA, osA = camera:WorldToViewportPoint(a.Position)
	local scB, osB = camera:WorldToViewportPoint(b.Position)
	if not osB then
		return true
	elseif not osA then
		return false
	end
	local v2a = Vector2.new(scA.X, scA.Y)
	local v2b = Vector2.new(scB.X, scB.Y)
	local cpos = Vector2.new(camera.ViewportSize.X/2, camera.ViewportSize.Y/2)
	if (cpos-v2a).Magnitude < (cpos-v2b).Magnitude then
		return true
	else
		return false
	end
    end)
end 

Here’s a video illustrating the problem. Notice how the errors start piling up once any of the golems are not in frame, since the sort function is being ran on RunService.Heartbeat. Right now, the error is invalid order function for sorting because it’s returning in one of the 3 sanity checks at the start of the function where something is being passed as nil, but would error out as a is a nil value or b is a nil value otherwise.

(please excuse the sad boi rap i was listening to when documenting this bug)

External Media
1 Like

Do you ever remove anything from self.Targetable?

Nah, the only thing that happens is that self.Targetable is defined again every 2 seconds in a while loop by calling CollectionService:GetTagged(“Targetable”) in case new mobs are added or something.

1 Like

I think the mistake is that your sorting function is just wrong. The documentation says that [the] order function ... must return true if the first argument should come first in the sorted array. (source). When both a and b are offscreen and your code reaches this point:

if not osB then
	return true

Whether or not a is less that b (and vice-versa) is dependent on the order in which they are compared.

A simpler way of reproducing this error is with the following code:

local t = {3, 4, 2, 1} table.sort(t, function(a, b) print(a, b) return true end)

This will be the output:

1 3
4 1
2 1
1 1
3 1
nil 1
03:16:49.341 - local t = {3, 4, 2, 1} table.sort(t, function(a, b) print(a, b) return true end):1: invalid order function for sorting

Right before erroring, nil is mysteriously being compared with 1 (someone with more knowhow could probably explain this better.)

Try using this sorting function instead. This ensures that, when both a and b are offscreen, their order isn’t undefined.

function(a, b)
	local scA, osA = camera:WorldToViewportPoint(a.Position)
	local scB, osB = camera:WorldToViewportPoint(b.Position)
	local v2a = Vector2.new(scA.X, scA.Y)
	local v2b = Vector2.new(scB.X, scB.Y)
	local cpos = Vector2.new(camera.ViewportSize.X/2, camera.ViewportSize.Y/2)
	local distA = (cpos-v2a).Magnitude
	local distB = (cpos-v2b).Magnitude
	-- case 1: both are offscreen. Order them by distance from center.
	if not osA and not osB then
		return distA < distB
	-- case 2: A is offscreen. B must be "closer"
	elseif not osA then
		return false
	-- case 3: B is offscreen. A must be "closer"
	elseif not osB then
		return true
	-- case 4: both are onscreen. Order them by distance from center.
	else
		return distA < distB
	end
end
3 Likes

I love you so much, thanks for this. The simple example makes so much sense, and I guess I misinterpreted the documentation and didn’t think to look back at it. :sweat_smile:

1 Like