Pcall doesn't catch error

In this function below, if the VertexID is invalid then :RemoveVertex() does an error, but an error still happens despite it being in a pcall.
image

1 Like

Check if the pcall is wrapping the intended code properly to catch errors.

What do you see in the output window when this code errors, can you show a screenshot of that?

image

Is this plugin code, and if so, are you sure you’re looking at the source for the code that’s actually running? That error looks like it’s coming from a call with no pcall wrapping it. I can’t repro this, pcall seems to catch that error just fine for me. If you click the error, does it bring you to outdated code, like a copy in PluginDebugService or something? Any Actor instances involved in this code?

current.mesh is an EditableMesh

no

My suspicion then is that some other EditableMesh code is calling RemoveVertex directly from C++, and that your pcall is not wrapping whatever Lua function call (assuming there is one) is at the top of that call stack. I think this because the error output you show has " - Studio" after it, which is a C++ calling context. If it were your code, you’d see " - Client - YourScriptName:218" after the error.

Try pcalling your entire mouseclick handler, see where the error throws. Maybe RemoveTriangle is what leads to this?

RemoveTriangle is what is meant to fix this issue, as it removes all triangles (faces) that the vertex could possibly be part of, allowing for the vertex to be deleted.

In your code, yes, but what I’m saying is that there is possibly a bug in Roblox’s C++ code where one of the Lua calls (e.g. RemoveTriangle) results in a call to RemoveVertex (from C++, not from your code) which then throws the error. So pcall will only catch it if the pcall wraps the Lua call that’s triggering it. i.e. your Lua call to RemoveVertex may not be the RemoveVertex call that is actually erroring.

The line of the error matches up to 218, the same line where current.mesh:RemoveVertex() is called

A repro file is probably the only thing that can resolve this. I don’t know how you know the line number, the error output (below) you posted was from a C++ internal call with no Lua script line number or stack trace :confused:. Either way, I think we’re missing critical context required to reproduce your results. I can’t get a pcall’d call to RemoveVertex to throw an uncaught error in any context (Client, Server, Edit or Plugin). You must be hitting some special edge case in Roblox’s code.

If neccesary, below is the entire source code for that localscript:

Source
 local lplr = game.Players.LocalPlayer
local mouse = lplr:GetMouse()
local keyboard = game.UserInputService

local camera = workspace.CurrentCamera
local keysOn : {[Enum.KeyCode]: boolean} = {}

local mainBase = script.Parent
local nameBox = mainBase.provinceName
local modeB = mainBase.modeB
local areaTXT = mainBase.areaTXT
local undoB = mainBase.undoV
local delB = mainBase.delB

local freeCam = require(game.ReplicatedStorage.freeCam)
local polyTrig = require(game.ReplicatedStorage.polyTrig)

local scale = require(game.ReplicatedStorage.scales)

local provinceYPadding = Vector3.yAxis * 0.1

for _, key: Enum.KeyCode  in Enum.KeyCode:GetEnumItems() do
	keysOn[key] = false
end

keyboard.InputBegan:Connect(function(i, r) if i.KeyCode and not r then keysOn[i.KeyCode] = true end end)
keyboard.InputEnded:Connect(function(i, r) if i.KeyCode and not r then keysOn[i.KeyCode] = false end end)


type tile = 
	{
		v: {[number]: Vector3},
		t: {number},
		p: MeshPart,
		mesh: EditableMesh,
		area: number
	}

local tileSet : {[string]: tile} = {}

local function FindClosestVertex(from:Vector3)
	local minDis = math.huge
	local closest = Vector3.zero
	local real = false
	for _, data in tileSet do
		for _, V in data.v do
			real = true
			local dis = (V-from).Magnitude
			if dis < minDis then
					minDis = dis
					closest = V
				end
			end
	end
	return minDis, closest, real
end

local MCparams = RaycastParams.new()
MCparams.FilterType = Enum.RaycastFilterType.Include
MCparams.FilterDescendantsInstances = {workspace.Baseplate}

local lastVertex


local function EnterCameraMode()
	freeCam.begin(nil, freeCam.presets.default)
end

local currentProvince = ''
local function NewProvince(core): tile
	lastVertex = nil
	local newMesh = game.ReplicatedStorage.MeshPart:Clone()
	local eMesh = Instance.new('EditableMesh')
	eMesh.Parent = newMesh
	newMesh.Parent = workspace.Editor
	local index = 'a'..math.random(1, 9999999)
	local tileData : tile = 
		{
			mesh = eMesh,
			p = newMesh,
			t = {},
			v = {},
			area = 0
		}
	currentProvince = index
	tileSet[index] = tileData
	return tileData
end

local function Rename(old, new)
	if tileSet[old] then
		tileSet[new] = tileSet[old]
		tileSet[old] = nil
		tileSet[new].p.Name = new
		if currentProvince == old then currentProvince = new end
	end
end

local tempPart = Instance.new('Part')
tempPart.CanCollide = false
tempPart.CanTouch = false
tempPart.CanQuery = false
tempPart.Size = Vector3.new(.1,.1,.1)
tempPart.Anchored = true
tempPart.Transparency = 0.7
tempPart.Color = Color3.fromRGB(200,50,50)
tempPart.Parent = workspace


local currentTarget = Vector3.zero
local xzRound = function(v:Vector3)
	local f = 0.1
	return Vector3.new(math.round(v.X/f)*f, v.Y, math.round(v.Z/f)*f)
end


mouse.Move:Connect(function()
	local mouseRay = workspace:Raycast(mouse.Origin.Position, mouse.Origin.LookVector * 190, MCparams)
	if mouseRay then
		local closestDis, tDis, real = FindClosestVertex(mouseRay.Position)
		local realPos =  xzRound(mouseRay.Position)
		if real then
			if closestDis < 0.25 then
				realPos  = xzRound(tDis)
				tempPart.Size = Vector3.new(.1, .3, .1)
			else
				tempPart.Size = Vector3.new(.1,.1,.1)
			end
		else
			tempPart.Size = Vector3.new(.1, .1, .1)
		end
		currentTarget = realPos
		tempPart.Position = realPos
	end
end)

local function RenderCurrent()
	local current = tileSet[currentProvince]
	if current then
		local outline= {}
		for ID, pos in current.v do
			table.insert(outline, {id = ID, pos = pos })
		end
		local triangles, area, timelen = polyTrig(outline)
		print('Took '..((timelen or 0) * 1000)..'ms')
		current.area = area
		areaTXT.Text = math.floor(area * scale.studKM / 1000) ..' km'
		for i, ID in ipairs(current.t) do
			current.mesh:RemoveTriangle(ID)
			table.remove(current.t, i)
		end
		for _, verti in triangles do
			local newTrig = current.mesh:AddTriangle(verti[1], verti[2], verti[3])
			table.insert(current.t, newTrig)
		end
		print(#current.t..' triangles')
	end
end
local function EnterEditMode()
	mouse.Button1Down:Connect(function()
		if currentProvince ~= '' then
			local current = tileSet[currentProvince]


			local newVertex = current.mesh:AddVertex(currentTarget + provinceYPadding)
			current.v[newVertex] = currentTarget + provinceYPadding
			RenderCurrent()
		end
	end)
end

nameBox:GetPropertyChangedSignal('Text'):Connect(function()
	if currentProvince ~= '' then
		Rename(currentProvince, nameBox.Text)
	end
end)

local modeBS = false

local MBCNCT : RBXScriptConnection
modeB.MouseButton1Click:Connect(function()
	if not modeBS then
		MBCNCT = mouse.Button1Down:Once(function()
			NewProvince(currentTarget + provinceYPadding)
			print('Created a new province!')
		end)
		modeB.Text = 'Editing province'
		modeB.BackgroundColor3 = Color3.fromRGB(255, 14, 14)
		modeBS = true
		undoB.Visible = true
		delB.Visible = true
	else
		if MBCNCT then MBCNCT:Disconnect(); MBCNCT = nil end
		currentProvince = ''
		modeBS = false
		modeB.BackgroundColor3 = Color3.fromRGB(30, 176, 255)
		modeB.Text = 'New Province'
		undoB.Visible = false
		delB.Visible = false
	end
end)

undoB.MouseButton1Click:Connect(function()
	local current = tileSet[currentProvince]
	if current then
		local latest = -1
		for i, v in pairs(current.v) do
			latest = i
		end
		for i, tid in ipairs(current.t) do
			local v0, v1, v2 = current.mesh:GetTriangleVertices(tid)
			if v0 == latest or v1 == latest or v2 == latest then
				table.remove(current.t, i)
				current.mesh:RemoveTriangle(tid)
			end
		end
		if latest == -1 then return end
		local suc, err = pcall(function()
			current.mesh:RemoveVertex(latest)
		end)
		if not suc then
			print('Cannot undo')
		else
			current.v[latest] = nil
			print('Undone vertex placement')
			RenderCurrent()
		end
	end
end)

delB.MouseButton1Click:Connect(function()
	if tileSet[currentProvince] then
		tileSet[currentProvince].p:Destroy()
		tileSet[currentProvince] = nil
		print('Deleted province: '..currentProvince..'!')
		currentProvince = ''
	end
end)

EnterEditMode()
EnterCameraMode()

When an error happens, the game pauses and the problematic script is viewed something like this:
image

Also it cannot be predicted when RemoveVertex() would error, sometimes the undo proccess is successful, other times it would work as fine.

When "Undone vertex placement" is printed, it shows that there was no error and that the vertex removal was succesful.
image

Ohhhhh, you have “On All Exceptions” checked in the debugger options, don’t you :sweat_smile:
image

When that is selected, you’ll break at all the errors, even pcall’d ones, because the debugger’s catch is higher priority than pcall, and that also explains why you saw the error output with " - Studio" after it… the debugger is echoing the error it caught.

If you change this to “On Unhandled Exceptions” (or “Never”) your pcall should catch the error like you expect.

I actually didn’t realize that the debugger didn’t propagate the error after catching it, I never use that middle option, my bad.

Hmm yeah this seems like the issue, how do I turn it off?

You just go to the SCRIPT tab (must have a script source open to see it) and choose one of the other 2 options. “Never” will never automatically break, it will only break at your manually-set breakpoints. “On Unhandled Exceptions” will only break on errors that your pcalls don’t handle; it moves the debugger catch hook to be lower priority than pcall. That is probably what you want. The “On All Exceptions” will break at any error before any pcall can catch it, so you can’t test any of your pcalls with that option enabled.

2 Likes

Oh by the way, I don’t think that the Vertex function sometimes errors because of that, but the failure seems random:
image

Right, so the C++ thing was a red herring. The error is only printing from C++ code because the debugger catches the error and then echos it to the output. Your error is not random though, there is a problem in your code in line 212 of your screenshot. You’re removing elements from your “current.t” table while iterating through it, which is a no-no because it invalidates the index (variable ‘i’). Your loop is supposed to remove all of the triangles that reference the "latest’ vertex, but because of the table.remove, it’s possible to skip over triangles that use that vertex (because table.remove compacts the array). Any time there are two triangles in the list that share the ‘latest’ vertex, and they are consecutive in the current.t array, only the first will be removed. So your code can then try to remove the vertex, without having removed all of the triangles that use it.

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